xref: /openbmc/linux/drivers/auxdisplay/charlcd.c (revision 88645a86)
1351f683bSMiguel Ojeda // SPDX-License-Identifier: GPL-2.0+
239f8ea46SGeert Uytterhoeven /*
339f8ea46SGeert Uytterhoeven  * Character LCD driver for Linux
439f8ea46SGeert Uytterhoeven  *
539f8ea46SGeert Uytterhoeven  * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
639f8ea46SGeert Uytterhoeven  * Copyright (C) 2016-2017 Glider bvba
739f8ea46SGeert Uytterhoeven  */
839f8ea46SGeert Uytterhoeven 
939f8ea46SGeert Uytterhoeven #include <linux/atomic.h>
10b34050faSMiguel Ojeda #include <linux/ctype.h>
1139f8ea46SGeert Uytterhoeven #include <linux/delay.h>
1239f8ea46SGeert Uytterhoeven #include <linux/fs.h>
1339f8ea46SGeert Uytterhoeven #include <linux/miscdevice.h>
1439f8ea46SGeert Uytterhoeven #include <linux/module.h>
1539f8ea46SGeert Uytterhoeven #include <linux/notifier.h>
1639f8ea46SGeert Uytterhoeven #include <linux/reboot.h>
1739f8ea46SGeert Uytterhoeven #include <linux/slab.h>
1839f8ea46SGeert Uytterhoeven #include <linux/uaccess.h>
1939f8ea46SGeert Uytterhoeven #include <linux/workqueue.h>
2039f8ea46SGeert Uytterhoeven 
2139f8ea46SGeert Uytterhoeven #include <generated/utsrelease.h>
2239f8ea46SGeert Uytterhoeven 
2375354284SMasahiro Yamada #include "charlcd.h"
242545c1c9SLars Poeschel #include "hd44780_common.h"
2539f8ea46SGeert Uytterhoeven 
2639f8ea46SGeert Uytterhoeven /* Keep the backlight on this many seconds for each flash */
2739f8ea46SGeert Uytterhoeven #define LCD_BL_TEMPO_PERIOD	4
2839f8ea46SGeert Uytterhoeven 
2939f8ea46SGeert Uytterhoeven #define LCD_FLAG_B		0x0004	/* Blink on */
3039f8ea46SGeert Uytterhoeven #define LCD_FLAG_C		0x0008	/* Cursor on */
3139f8ea46SGeert Uytterhoeven #define LCD_FLAG_D		0x0010	/* Display on */
3239f8ea46SGeert Uytterhoeven #define LCD_FLAG_F		0x0020	/* Large font mode */
3339f8ea46SGeert Uytterhoeven #define LCD_FLAG_N		0x0040	/* 2-rows mode */
3439f8ea46SGeert Uytterhoeven #define LCD_FLAG_L		0x0080	/* Backlight enabled */
3539f8ea46SGeert Uytterhoeven 
3639f8ea46SGeert Uytterhoeven /* LCD commands */
3739f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CLEAR	0x01	/* Clear entire display */
3839f8ea46SGeert Uytterhoeven 
3939f8ea46SGeert Uytterhoeven #define LCD_CMD_ENTRY_MODE	0x04	/* Set entry mode */
4039f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_INC	0x02	/* Increment cursor */
4139f8ea46SGeert Uytterhoeven 
4239f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_CTRL	0x08	/* Display control */
4339f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_ON	0x04	/* Set display on */
4439f8ea46SGeert Uytterhoeven #define LCD_CMD_CURSOR_ON	0x02	/* Set cursor on */
4539f8ea46SGeert Uytterhoeven #define LCD_CMD_BLINK_ON	0x01	/* Set blink on */
4639f8ea46SGeert Uytterhoeven 
4739f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT		0x10	/* Shift cursor/display */
4839f8ea46SGeert Uytterhoeven #define LCD_CMD_DISPLAY_SHIFT	0x08	/* Shift display instead of cursor */
4939f8ea46SGeert Uytterhoeven #define LCD_CMD_SHIFT_RIGHT	0x04	/* Shift display/cursor to the right */
5039f8ea46SGeert Uytterhoeven 
5139f8ea46SGeert Uytterhoeven #define LCD_CMD_FUNCTION_SET	0x20	/* Set function */
5239f8ea46SGeert Uytterhoeven #define LCD_CMD_DATA_LEN_8BITS	0x10	/* Set data length to 8 bits */
5339f8ea46SGeert Uytterhoeven #define LCD_CMD_TWO_LINES	0x08	/* Set to two display lines */
5439f8ea46SGeert Uytterhoeven #define LCD_CMD_FONT_5X10_DOTS	0x04	/* Set char font to 5x10 dots */
5539f8ea46SGeert Uytterhoeven 
5639f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_CGRAM_ADDR	0x40	/* Set char generator RAM address */
5739f8ea46SGeert Uytterhoeven 
5839f8ea46SGeert Uytterhoeven #define LCD_CMD_SET_DDRAM_ADDR 0x80    /* Set display data RAM address */
5939f8ea46SGeert Uytterhoeven 
6039f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_LEN		24	/* Max chars for LCD escape command */
6139f8ea46SGeert Uytterhoeven #define LCD_ESCAPE_CHAR		27	/* Use char 27 for escape command */
6239f8ea46SGeert Uytterhoeven 
6339f8ea46SGeert Uytterhoeven struct charlcd_priv {
6439f8ea46SGeert Uytterhoeven 	struct charlcd lcd;
6539f8ea46SGeert Uytterhoeven 
6639f8ea46SGeert Uytterhoeven 	struct delayed_work bl_work;
6739f8ea46SGeert Uytterhoeven 	struct mutex bl_tempo_lock;	/* Protects access to bl_tempo */
6839f8ea46SGeert Uytterhoeven 	bool bl_tempo;
6939f8ea46SGeert Uytterhoeven 
7039f8ea46SGeert Uytterhoeven 	bool must_clear;
7139f8ea46SGeert Uytterhoeven 
7239f8ea46SGeert Uytterhoeven 	/* contains the LCD config state */
7339f8ea46SGeert Uytterhoeven 	unsigned long int flags;
7439f8ea46SGeert Uytterhoeven 
7539f8ea46SGeert Uytterhoeven 	/* Current escape sequence and it's length or -1 if outside */
7639f8ea46SGeert Uytterhoeven 	struct {
7739f8ea46SGeert Uytterhoeven 		char buf[LCD_ESCAPE_LEN + 1];
7839f8ea46SGeert Uytterhoeven 		int len;
7939f8ea46SGeert Uytterhoeven 	} esc_seq;
8039f8ea46SGeert Uytterhoeven 
812f920c0fSGustavo A. R. Silva 	unsigned long long drvdata[];
8239f8ea46SGeert Uytterhoeven };
8339f8ea46SGeert Uytterhoeven 
84b658a211SAndy Shevchenko #define charlcd_to_priv(p)	container_of(p, struct charlcd_priv, lcd)
8539f8ea46SGeert Uytterhoeven 
8639f8ea46SGeert Uytterhoeven /* Device single-open policy control */
8739f8ea46SGeert Uytterhoeven static atomic_t charlcd_available = ATOMIC_INIT(1);
8839f8ea46SGeert Uytterhoeven 
8939f8ea46SGeert Uytterhoeven /* sleeps that many milliseconds with a reschedule */
9039f8ea46SGeert Uytterhoeven static void long_sleep(int ms)
9139f8ea46SGeert Uytterhoeven {
9239f8ea46SGeert Uytterhoeven 	schedule_timeout_interruptible(msecs_to_jiffies(ms));
9339f8ea46SGeert Uytterhoeven }
9439f8ea46SGeert Uytterhoeven 
9539f8ea46SGeert Uytterhoeven /* turn the backlight on or off */
9666ce7d5cSLars Poeschel static void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
9739f8ea46SGeert Uytterhoeven {
98b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
9939f8ea46SGeert Uytterhoeven 
10039f8ea46SGeert Uytterhoeven 	if (!lcd->ops->backlight)
10139f8ea46SGeert Uytterhoeven 		return;
10239f8ea46SGeert Uytterhoeven 
10339f8ea46SGeert Uytterhoeven 	mutex_lock(&priv->bl_tempo_lock);
10439f8ea46SGeert Uytterhoeven 	if (!priv->bl_tempo)
10539f8ea46SGeert Uytterhoeven 		lcd->ops->backlight(lcd, on);
10639f8ea46SGeert Uytterhoeven 	mutex_unlock(&priv->bl_tempo_lock);
10739f8ea46SGeert Uytterhoeven }
10839f8ea46SGeert Uytterhoeven 
10939f8ea46SGeert Uytterhoeven static void charlcd_bl_off(struct work_struct *work)
11039f8ea46SGeert Uytterhoeven {
11139f8ea46SGeert Uytterhoeven 	struct delayed_work *dwork = to_delayed_work(work);
11239f8ea46SGeert Uytterhoeven 	struct charlcd_priv *priv =
11339f8ea46SGeert Uytterhoeven 		container_of(dwork, struct charlcd_priv, bl_work);
11439f8ea46SGeert Uytterhoeven 
11539f8ea46SGeert Uytterhoeven 	mutex_lock(&priv->bl_tempo_lock);
11639f8ea46SGeert Uytterhoeven 	if (priv->bl_tempo) {
11739f8ea46SGeert Uytterhoeven 		priv->bl_tempo = false;
11839f8ea46SGeert Uytterhoeven 		if (!(priv->flags & LCD_FLAG_L))
11939f8ea46SGeert Uytterhoeven 			priv->lcd.ops->backlight(&priv->lcd, 0);
12039f8ea46SGeert Uytterhoeven 	}
12139f8ea46SGeert Uytterhoeven 	mutex_unlock(&priv->bl_tempo_lock);
12239f8ea46SGeert Uytterhoeven }
12339f8ea46SGeert Uytterhoeven 
12439f8ea46SGeert Uytterhoeven /* turn the backlight on for a little while */
12539f8ea46SGeert Uytterhoeven void charlcd_poke(struct charlcd *lcd)
12639f8ea46SGeert Uytterhoeven {
127b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
12839f8ea46SGeert Uytterhoeven 
12939f8ea46SGeert Uytterhoeven 	if (!lcd->ops->backlight)
13039f8ea46SGeert Uytterhoeven 		return;
13139f8ea46SGeert Uytterhoeven 
13239f8ea46SGeert Uytterhoeven 	cancel_delayed_work_sync(&priv->bl_work);
13339f8ea46SGeert Uytterhoeven 
13439f8ea46SGeert Uytterhoeven 	mutex_lock(&priv->bl_tempo_lock);
13539f8ea46SGeert Uytterhoeven 	if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
13639f8ea46SGeert Uytterhoeven 		lcd->ops->backlight(lcd, 1);
13739f8ea46SGeert Uytterhoeven 	priv->bl_tempo = true;
13839f8ea46SGeert Uytterhoeven 	schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
13939f8ea46SGeert Uytterhoeven 	mutex_unlock(&priv->bl_tempo_lock);
14039f8ea46SGeert Uytterhoeven }
14139f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_poke);
14239f8ea46SGeert Uytterhoeven 
14339f8ea46SGeert Uytterhoeven static void charlcd_home(struct charlcd *lcd)
14439f8ea46SGeert Uytterhoeven {
14511588b59SLars Poeschel 	lcd->addr.x = 0;
14611588b59SLars Poeschel 	lcd->addr.y = 0;
147*88645a86SLars Poeschel 	lcd->ops->home(lcd);
14839f8ea46SGeert Uytterhoeven }
14939f8ea46SGeert Uytterhoeven 
15039f8ea46SGeert Uytterhoeven static void charlcd_print(struct charlcd *lcd, char c)
15139f8ea46SGeert Uytterhoeven {
152d3a2fb81SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
153d3a2fb81SLars Poeschel 
15439f8ea46SGeert Uytterhoeven 	if (lcd->char_conv)
15539f8ea46SGeert Uytterhoeven 		c = lcd->char_conv[(unsigned char)c];
156b26deabbSLars Poeschel 
157b26deabbSLars Poeschel 	if (!lcd->ops->print(lcd, c))
15811588b59SLars Poeschel 		lcd->addr.x++;
15954bc937fSSean Young 
16039f8ea46SGeert Uytterhoeven 	/* prevents the cursor from wrapping onto the next line */
161d3a2fb81SLars Poeschel 	if (lcd->addr.x == hdc->bwidth)
162d3a2fb81SLars Poeschel 		lcd->ops->gotoxy(lcd);
16339f8ea46SGeert Uytterhoeven }
16439f8ea46SGeert Uytterhoeven 
16539f8ea46SGeert Uytterhoeven static void charlcd_clear_fast(struct charlcd *lcd)
16639f8ea46SGeert Uytterhoeven {
1672545c1c9SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
16839f8ea46SGeert Uytterhoeven 	int pos;
16939f8ea46SGeert Uytterhoeven 
17039f8ea46SGeert Uytterhoeven 	charlcd_home(lcd);
17139f8ea46SGeert Uytterhoeven 
17239f8ea46SGeert Uytterhoeven 	if (lcd->ops->clear_fast)
17339f8ea46SGeert Uytterhoeven 		lcd->ops->clear_fast(lcd);
17439f8ea46SGeert Uytterhoeven 	else
1752545c1c9SLars Poeschel 		for (pos = 0; pos < min(2, lcd->height) * hdc->hwidth; pos++)
176b26deabbSLars Poeschel 			lcd->ops->print(lcd, ' ');
17739f8ea46SGeert Uytterhoeven 
17839f8ea46SGeert Uytterhoeven 	charlcd_home(lcd);
17939f8ea46SGeert Uytterhoeven }
18039f8ea46SGeert Uytterhoeven 
18139f8ea46SGeert Uytterhoeven /* clears the display and resets X/Y */
18239f8ea46SGeert Uytterhoeven static void charlcd_clear_display(struct charlcd *lcd)
18339f8ea46SGeert Uytterhoeven {
1842c6a82f2SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
18539f8ea46SGeert Uytterhoeven 
1862c6a82f2SLars Poeschel 	hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CLEAR);
18711588b59SLars Poeschel 	lcd->addr.x = 0;
18811588b59SLars Poeschel 	lcd->addr.y = 0;
18939f8ea46SGeert Uytterhoeven 	/* we must wait a few milliseconds (15) */
19039f8ea46SGeert Uytterhoeven 	long_sleep(15);
19139f8ea46SGeert Uytterhoeven }
19239f8ea46SGeert Uytterhoeven 
19339f8ea46SGeert Uytterhoeven static int charlcd_init_display(struct charlcd *lcd)
19439f8ea46SGeert Uytterhoeven {
1952c6a82f2SLars Poeschel 	void (*write_cmd_raw)(struct hd44780_common *hdc, int cmd);
196b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
1973fc04dd7SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
198ac201479SGeert Uytterhoeven 	u8 init;
199ac201479SGeert Uytterhoeven 
2003fc04dd7SLars Poeschel 	if (hdc->ifwidth != 4 && hdc->ifwidth != 8)
201ac201479SGeert Uytterhoeven 		return -EINVAL;
20239f8ea46SGeert Uytterhoeven 
20339f8ea46SGeert Uytterhoeven 	priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
20439f8ea46SGeert Uytterhoeven 		      LCD_FLAG_C | LCD_FLAG_B;
20539f8ea46SGeert Uytterhoeven 
20639f8ea46SGeert Uytterhoeven 	long_sleep(20);		/* wait 20 ms after power-up for the paranoid */
20739f8ea46SGeert Uytterhoeven 
208ac201479SGeert Uytterhoeven 	/*
209ac201479SGeert Uytterhoeven 	 * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
210ac201479SGeert Uytterhoeven 	 * the LCD is in 8-bit mode afterwards
211ac201479SGeert Uytterhoeven 	 */
212ac201479SGeert Uytterhoeven 	init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
2133fc04dd7SLars Poeschel 	if (hdc->ifwidth == 4) {
214ac201479SGeert Uytterhoeven 		init >>= 4;
2152c6a82f2SLars Poeschel 		write_cmd_raw = hdc->write_cmd_raw4;
216ac201479SGeert Uytterhoeven 	} else {
2172c6a82f2SLars Poeschel 		write_cmd_raw = hdc->write_cmd;
218ac201479SGeert Uytterhoeven 	}
2192c6a82f2SLars Poeschel 	write_cmd_raw(hdc, init);
22039f8ea46SGeert Uytterhoeven 	long_sleep(10);
2212c6a82f2SLars Poeschel 	write_cmd_raw(hdc, init);
22239f8ea46SGeert Uytterhoeven 	long_sleep(10);
2232c6a82f2SLars Poeschel 	write_cmd_raw(hdc, init);
22439f8ea46SGeert Uytterhoeven 	long_sleep(10);
22539f8ea46SGeert Uytterhoeven 
2263fc04dd7SLars Poeschel 	if (hdc->ifwidth == 4) {
227ac201479SGeert Uytterhoeven 		/* Switch to 4-bit mode, 1 line, small fonts */
2282c6a82f2SLars Poeschel 		hdc->write_cmd_raw4(hdc, LCD_CMD_FUNCTION_SET >> 4);
229ac201479SGeert Uytterhoeven 		long_sleep(10);
230ac201479SGeert Uytterhoeven 	}
231ac201479SGeert Uytterhoeven 
23239f8ea46SGeert Uytterhoeven 	/* set font height and lines number */
2332c6a82f2SLars Poeschel 	hdc->write_cmd(hdc,
234ac201479SGeert Uytterhoeven 		LCD_CMD_FUNCTION_SET |
2353fc04dd7SLars Poeschel 		((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
23639f8ea46SGeert Uytterhoeven 		((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
23739f8ea46SGeert Uytterhoeven 		((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
23839f8ea46SGeert Uytterhoeven 	long_sleep(10);
23939f8ea46SGeert Uytterhoeven 
24039f8ea46SGeert Uytterhoeven 	/* display off, cursor off, blink off */
2412c6a82f2SLars Poeschel 	hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CTRL);
24239f8ea46SGeert Uytterhoeven 	long_sleep(10);
24339f8ea46SGeert Uytterhoeven 
2442c6a82f2SLars Poeschel 	hdc->write_cmd(hdc,
24539f8ea46SGeert Uytterhoeven 		LCD_CMD_DISPLAY_CTRL |	/* set display mode */
24639f8ea46SGeert Uytterhoeven 		((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
24739f8ea46SGeert Uytterhoeven 		((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
24839f8ea46SGeert Uytterhoeven 		((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
24939f8ea46SGeert Uytterhoeven 
25039f8ea46SGeert Uytterhoeven 	charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
25139f8ea46SGeert Uytterhoeven 
25239f8ea46SGeert Uytterhoeven 	long_sleep(10);
25339f8ea46SGeert Uytterhoeven 
25439f8ea46SGeert Uytterhoeven 	/* entry mode set : increment, cursor shifting */
2552c6a82f2SLars Poeschel 	hdc->write_cmd(hdc, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
25639f8ea46SGeert Uytterhoeven 
25739f8ea46SGeert Uytterhoeven 	charlcd_clear_display(lcd);
25839f8ea46SGeert Uytterhoeven 	return 0;
25939f8ea46SGeert Uytterhoeven }
26039f8ea46SGeert Uytterhoeven 
26139f8ea46SGeert Uytterhoeven /*
262b34050faSMiguel Ojeda  * Parses a movement command of the form "(.*);", where the group can be
263b34050faSMiguel Ojeda  * any number of subcommands of the form "(x|y)[0-9]+".
264b34050faSMiguel Ojeda  *
265b34050faSMiguel Ojeda  * Returns whether the command is valid. The position arguments are
266b34050faSMiguel Ojeda  * only written if the parsing was successful.
267b34050faSMiguel Ojeda  *
268b34050faSMiguel Ojeda  * For instance:
269b34050faSMiguel Ojeda  *   - ";"          returns (<original x>, <original y>).
270b34050faSMiguel Ojeda  *   - "x1;"        returns (1, <original y>).
271b34050faSMiguel Ojeda  *   - "y2x1;"      returns (1, 2).
272b34050faSMiguel Ojeda  *   - "x12y34x56;" returns (56, 34).
273b34050faSMiguel Ojeda  *   - ""           fails.
274b34050faSMiguel Ojeda  *   - "x"          fails.
275b34050faSMiguel Ojeda  *   - "x;"         fails.
276b34050faSMiguel Ojeda  *   - "x1"         fails.
277b34050faSMiguel Ojeda  *   - "xy12;"      fails.
278b34050faSMiguel Ojeda  *   - "x12yy12;"   fails.
279b34050faSMiguel Ojeda  *   - "xx"         fails.
280b34050faSMiguel Ojeda  */
281b34050faSMiguel Ojeda static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
282b34050faSMiguel Ojeda {
283b34050faSMiguel Ojeda 	unsigned long new_x = *x;
284b34050faSMiguel Ojeda 	unsigned long new_y = *y;
285d717e7daSAndy Shevchenko 	char *p;
286b34050faSMiguel Ojeda 
287b34050faSMiguel Ojeda 	for (;;) {
288b34050faSMiguel Ojeda 		if (!*s)
289b34050faSMiguel Ojeda 			return false;
290b34050faSMiguel Ojeda 
291b34050faSMiguel Ojeda 		if (*s == ';')
292b34050faSMiguel Ojeda 			break;
293b34050faSMiguel Ojeda 
294b34050faSMiguel Ojeda 		if (*s == 'x') {
295d717e7daSAndy Shevchenko 			new_x = simple_strtoul(s + 1, &p, 10);
296d717e7daSAndy Shevchenko 			if (p == s + 1)
297b34050faSMiguel Ojeda 				return false;
298d717e7daSAndy Shevchenko 			s = p;
299b34050faSMiguel Ojeda 		} else if (*s == 'y') {
300d717e7daSAndy Shevchenko 			new_y = simple_strtoul(s + 1, &p, 10);
301d717e7daSAndy Shevchenko 			if (p == s + 1)
302b34050faSMiguel Ojeda 				return false;
303d717e7daSAndy Shevchenko 			s = p;
304b34050faSMiguel Ojeda 		} else {
305b34050faSMiguel Ojeda 			return false;
306b34050faSMiguel Ojeda 		}
307b34050faSMiguel Ojeda 	}
308b34050faSMiguel Ojeda 
309b34050faSMiguel Ojeda 	*x = new_x;
310b34050faSMiguel Ojeda 	*y = new_y;
311b34050faSMiguel Ojeda 	return true;
312b34050faSMiguel Ojeda }
313b34050faSMiguel Ojeda 
314b34050faSMiguel Ojeda /*
31539f8ea46SGeert Uytterhoeven  * These are the file operation function for user access to /dev/lcd
31639f8ea46SGeert Uytterhoeven  * This function can also be called from inside the kernel, by
31739f8ea46SGeert Uytterhoeven  * setting file and ppos to NULL.
31839f8ea46SGeert Uytterhoeven  *
31939f8ea46SGeert Uytterhoeven  */
32039f8ea46SGeert Uytterhoeven 
32139f8ea46SGeert Uytterhoeven static inline int handle_lcd_special_code(struct charlcd *lcd)
32239f8ea46SGeert Uytterhoeven {
323b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
3242545c1c9SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
32539f8ea46SGeert Uytterhoeven 
32639f8ea46SGeert Uytterhoeven 	/* LCD special codes */
32739f8ea46SGeert Uytterhoeven 
32839f8ea46SGeert Uytterhoeven 	int processed = 0;
32939f8ea46SGeert Uytterhoeven 
33039f8ea46SGeert Uytterhoeven 	char *esc = priv->esc_seq.buf + 2;
33139f8ea46SGeert Uytterhoeven 	int oldflags = priv->flags;
33239f8ea46SGeert Uytterhoeven 
33339f8ea46SGeert Uytterhoeven 	/* check for display mode flags */
33439f8ea46SGeert Uytterhoeven 	switch (*esc) {
33539f8ea46SGeert Uytterhoeven 	case 'D':	/* Display ON */
33639f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_D;
33739f8ea46SGeert Uytterhoeven 		processed = 1;
33839f8ea46SGeert Uytterhoeven 		break;
33939f8ea46SGeert Uytterhoeven 	case 'd':	/* Display OFF */
34039f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_D;
34139f8ea46SGeert Uytterhoeven 		processed = 1;
34239f8ea46SGeert Uytterhoeven 		break;
34339f8ea46SGeert Uytterhoeven 	case 'C':	/* Cursor ON */
34439f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_C;
34539f8ea46SGeert Uytterhoeven 		processed = 1;
34639f8ea46SGeert Uytterhoeven 		break;
34739f8ea46SGeert Uytterhoeven 	case 'c':	/* Cursor OFF */
34839f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_C;
34939f8ea46SGeert Uytterhoeven 		processed = 1;
35039f8ea46SGeert Uytterhoeven 		break;
35139f8ea46SGeert Uytterhoeven 	case 'B':	/* Blink ON */
35239f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_B;
35339f8ea46SGeert Uytterhoeven 		processed = 1;
35439f8ea46SGeert Uytterhoeven 		break;
35539f8ea46SGeert Uytterhoeven 	case 'b':	/* Blink OFF */
35639f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_B;
35739f8ea46SGeert Uytterhoeven 		processed = 1;
35839f8ea46SGeert Uytterhoeven 		break;
35939f8ea46SGeert Uytterhoeven 	case '+':	/* Back light ON */
36039f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_L;
36139f8ea46SGeert Uytterhoeven 		processed = 1;
36239f8ea46SGeert Uytterhoeven 		break;
36339f8ea46SGeert Uytterhoeven 	case '-':	/* Back light OFF */
36439f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_L;
36539f8ea46SGeert Uytterhoeven 		processed = 1;
36639f8ea46SGeert Uytterhoeven 		break;
36739f8ea46SGeert Uytterhoeven 	case '*':	/* Flash back light */
36839f8ea46SGeert Uytterhoeven 		charlcd_poke(lcd);
36939f8ea46SGeert Uytterhoeven 		processed = 1;
37039f8ea46SGeert Uytterhoeven 		break;
37139f8ea46SGeert Uytterhoeven 	case 'f':	/* Small Font */
37239f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_F;
37339f8ea46SGeert Uytterhoeven 		processed = 1;
37439f8ea46SGeert Uytterhoeven 		break;
37539f8ea46SGeert Uytterhoeven 	case 'F':	/* Large Font */
37639f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_F;
37739f8ea46SGeert Uytterhoeven 		processed = 1;
37839f8ea46SGeert Uytterhoeven 		break;
37939f8ea46SGeert Uytterhoeven 	case 'n':	/* One Line */
38039f8ea46SGeert Uytterhoeven 		priv->flags &= ~LCD_FLAG_N;
38139f8ea46SGeert Uytterhoeven 		processed = 1;
38239f8ea46SGeert Uytterhoeven 		break;
38339f8ea46SGeert Uytterhoeven 	case 'N':	/* Two Lines */
38439f8ea46SGeert Uytterhoeven 		priv->flags |= LCD_FLAG_N;
38599b9b490SRobert Abel 		processed = 1;
38639f8ea46SGeert Uytterhoeven 		break;
38739f8ea46SGeert Uytterhoeven 	case 'l':	/* Shift Cursor Left */
38811588b59SLars Poeschel 		if (lcd->addr.x > 0) {
38939f8ea46SGeert Uytterhoeven 			/* back one char if not at end of line */
39011588b59SLars Poeschel 			if (lcd->addr.x < hdc->bwidth)
3912c6a82f2SLars Poeschel 				hdc->write_cmd(hdc, LCD_CMD_SHIFT);
39211588b59SLars Poeschel 			lcd->addr.x--;
39339f8ea46SGeert Uytterhoeven 		}
39439f8ea46SGeert Uytterhoeven 		processed = 1;
39539f8ea46SGeert Uytterhoeven 		break;
39639f8ea46SGeert Uytterhoeven 	case 'r':	/* shift cursor right */
39711588b59SLars Poeschel 		if (lcd->addr.x < lcd->width) {
39839f8ea46SGeert Uytterhoeven 			/* allow the cursor to pass the end of the line */
39911588b59SLars Poeschel 			if (lcd->addr.x < (hdc->bwidth - 1))
4002c6a82f2SLars Poeschel 				hdc->write_cmd(hdc,
40139f8ea46SGeert Uytterhoeven 					LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
40211588b59SLars Poeschel 			lcd->addr.x++;
40339f8ea46SGeert Uytterhoeven 		}
40439f8ea46SGeert Uytterhoeven 		processed = 1;
40539f8ea46SGeert Uytterhoeven 		break;
40639f8ea46SGeert Uytterhoeven 	case 'L':	/* shift display left */
4072c6a82f2SLars Poeschel 		hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
40839f8ea46SGeert Uytterhoeven 		processed = 1;
40939f8ea46SGeert Uytterhoeven 		break;
41039f8ea46SGeert Uytterhoeven 	case 'R':	/* shift display right */
4112c6a82f2SLars Poeschel 		hdc->write_cmd(hdc,
41239f8ea46SGeert Uytterhoeven 				    LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
41339f8ea46SGeert Uytterhoeven 				    LCD_CMD_SHIFT_RIGHT);
41439f8ea46SGeert Uytterhoeven 		processed = 1;
41539f8ea46SGeert Uytterhoeven 		break;
41639f8ea46SGeert Uytterhoeven 	case 'k': {	/* kill end of line */
417b26deabbSLars Poeschel 		int x, xs, ys;
41839f8ea46SGeert Uytterhoeven 
419b26deabbSLars Poeschel 		xs = lcd->addr.x;
420b26deabbSLars Poeschel 		ys = lcd->addr.y;
42111588b59SLars Poeschel 		for (x = lcd->addr.x; x < hdc->bwidth; x++)
422b26deabbSLars Poeschel 			lcd->ops->print(lcd, ' ');
42339f8ea46SGeert Uytterhoeven 
42439f8ea46SGeert Uytterhoeven 		/* restore cursor position */
425b26deabbSLars Poeschel 		lcd->addr.x = xs;
426b26deabbSLars Poeschel 		lcd->addr.y = ys;
427d3a2fb81SLars Poeschel 		lcd->ops->gotoxy(lcd);
42839f8ea46SGeert Uytterhoeven 		processed = 1;
42939f8ea46SGeert Uytterhoeven 		break;
43039f8ea46SGeert Uytterhoeven 	}
43139f8ea46SGeert Uytterhoeven 	case 'I':	/* reinitialize display */
43239f8ea46SGeert Uytterhoeven 		charlcd_init_display(lcd);
43339f8ea46SGeert Uytterhoeven 		processed = 1;
43439f8ea46SGeert Uytterhoeven 		break;
43539f8ea46SGeert Uytterhoeven 	case 'G': {
43639f8ea46SGeert Uytterhoeven 		/* Generator : LGcxxxxx...xx; must have <c> between '0'
43739f8ea46SGeert Uytterhoeven 		 * and '7', representing the numerical ASCII code of the
43839f8ea46SGeert Uytterhoeven 		 * redefined character, and <xx...xx> a sequence of 16
43939f8ea46SGeert Uytterhoeven 		 * hex digits representing 8 bytes for each character.
44039f8ea46SGeert Uytterhoeven 		 * Most LCDs will only use 5 lower bits of the 7 first
44139f8ea46SGeert Uytterhoeven 		 * bytes.
44239f8ea46SGeert Uytterhoeven 		 */
44339f8ea46SGeert Uytterhoeven 
44439f8ea46SGeert Uytterhoeven 		unsigned char cgbytes[8];
44539f8ea46SGeert Uytterhoeven 		unsigned char cgaddr;
44639f8ea46SGeert Uytterhoeven 		int cgoffset;
44739f8ea46SGeert Uytterhoeven 		int shift;
44839f8ea46SGeert Uytterhoeven 		char value;
44939f8ea46SGeert Uytterhoeven 		int addr;
45039f8ea46SGeert Uytterhoeven 
45139f8ea46SGeert Uytterhoeven 		if (!strchr(esc, ';'))
45239f8ea46SGeert Uytterhoeven 			break;
45339f8ea46SGeert Uytterhoeven 
45439f8ea46SGeert Uytterhoeven 		esc++;
45539f8ea46SGeert Uytterhoeven 
45639f8ea46SGeert Uytterhoeven 		cgaddr = *(esc++) - '0';
45739f8ea46SGeert Uytterhoeven 		if (cgaddr > 7) {
45839f8ea46SGeert Uytterhoeven 			processed = 1;
45939f8ea46SGeert Uytterhoeven 			break;
46039f8ea46SGeert Uytterhoeven 		}
46139f8ea46SGeert Uytterhoeven 
46239f8ea46SGeert Uytterhoeven 		cgoffset = 0;
46339f8ea46SGeert Uytterhoeven 		shift = 0;
46439f8ea46SGeert Uytterhoeven 		value = 0;
46539f8ea46SGeert Uytterhoeven 		while (*esc && cgoffset < 8) {
4663f03b649SAndy Shevchenko 			int half;
46739f8ea46SGeert Uytterhoeven 
4683f03b649SAndy Shevchenko 			shift ^= 4;
4693f03b649SAndy Shevchenko 
4703f03b649SAndy Shevchenko 			half = hex_to_bin(*esc++);
4713f03b649SAndy Shevchenko 			if (half < 0)
4723f03b649SAndy Shevchenko 				continue;
4733f03b649SAndy Shevchenko 
4743f03b649SAndy Shevchenko 			value |= half << shift;
47539f8ea46SGeert Uytterhoeven 			if (shift == 0) {
47639f8ea46SGeert Uytterhoeven 				cgbytes[cgoffset++] = value;
47739f8ea46SGeert Uytterhoeven 				value = 0;
47839f8ea46SGeert Uytterhoeven 			}
47939f8ea46SGeert Uytterhoeven 		}
48039f8ea46SGeert Uytterhoeven 
4812c6a82f2SLars Poeschel 		hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
48239f8ea46SGeert Uytterhoeven 		for (addr = 0; addr < cgoffset; addr++)
48371ff701bSLars Poeschel 			hdc->write_data(hdc, cgbytes[addr]);
48439f8ea46SGeert Uytterhoeven 
48539f8ea46SGeert Uytterhoeven 		/* ensures that we stop writing to CGRAM */
486d3a2fb81SLars Poeschel 		lcd->ops->gotoxy(lcd);
48739f8ea46SGeert Uytterhoeven 		processed = 1;
48839f8ea46SGeert Uytterhoeven 		break;
48939f8ea46SGeert Uytterhoeven 	}
49039f8ea46SGeert Uytterhoeven 	case 'x':	/* gotoxy : LxXXX[yYYY]; */
49139f8ea46SGeert Uytterhoeven 	case 'y':	/* gotoxy : LyYYY[xXXX]; */
4929bc30ab8SMans Rullgard 		if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
4939bc30ab8SMans Rullgard 			break;
4949bc30ab8SMans Rullgard 
495b34050faSMiguel Ojeda 		/* If the command is valid, move to the new address */
49611588b59SLars Poeschel 		if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
497d3a2fb81SLars Poeschel 			lcd->ops->gotoxy(lcd);
498b34050faSMiguel Ojeda 
499b34050faSMiguel Ojeda 		/* Regardless of its validity, mark as processed */
50039f8ea46SGeert Uytterhoeven 		processed = 1;
50139f8ea46SGeert Uytterhoeven 		break;
50239f8ea46SGeert Uytterhoeven 	}
50339f8ea46SGeert Uytterhoeven 
50439f8ea46SGeert Uytterhoeven 	/* TODO: This indent party here got ugly, clean it! */
50539f8ea46SGeert Uytterhoeven 	/* Check whether one flag was changed */
50639f8ea46SGeert Uytterhoeven 	if (oldflags == priv->flags)
50739f8ea46SGeert Uytterhoeven 		return processed;
50839f8ea46SGeert Uytterhoeven 
50939f8ea46SGeert Uytterhoeven 	/* check whether one of B,C,D flags were changed */
51039f8ea46SGeert Uytterhoeven 	if ((oldflags ^ priv->flags) &
51139f8ea46SGeert Uytterhoeven 	    (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
51239f8ea46SGeert Uytterhoeven 		/* set display mode */
5132c6a82f2SLars Poeschel 		hdc->write_cmd(hdc,
51439f8ea46SGeert Uytterhoeven 			LCD_CMD_DISPLAY_CTRL |
51539f8ea46SGeert Uytterhoeven 			((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
51639f8ea46SGeert Uytterhoeven 			((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
51739f8ea46SGeert Uytterhoeven 			((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
51839f8ea46SGeert Uytterhoeven 	/* check whether one of F,N flags was changed */
51939f8ea46SGeert Uytterhoeven 	else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
5202c6a82f2SLars Poeschel 		hdc->write_cmd(hdc,
521ac201479SGeert Uytterhoeven 			LCD_CMD_FUNCTION_SET |
5223fc04dd7SLars Poeschel 			((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
52339f8ea46SGeert Uytterhoeven 			((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
52439f8ea46SGeert Uytterhoeven 			((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
52539f8ea46SGeert Uytterhoeven 	/* check whether L flag was changed */
52639f8ea46SGeert Uytterhoeven 	else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
52739f8ea46SGeert Uytterhoeven 		charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
52839f8ea46SGeert Uytterhoeven 
52939f8ea46SGeert Uytterhoeven 	return processed;
53039f8ea46SGeert Uytterhoeven }
53139f8ea46SGeert Uytterhoeven 
53239f8ea46SGeert Uytterhoeven static void charlcd_write_char(struct charlcd *lcd, char c)
53339f8ea46SGeert Uytterhoeven {
534b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
5352545c1c9SLars Poeschel 	struct hd44780_common *hdc = lcd->drvdata;
53639f8ea46SGeert Uytterhoeven 
53739f8ea46SGeert Uytterhoeven 	/* first, we'll test if we're in escape mode */
53839f8ea46SGeert Uytterhoeven 	if ((c != '\n') && priv->esc_seq.len >= 0) {
53939f8ea46SGeert Uytterhoeven 		/* yes, let's add this char to the buffer */
54039f8ea46SGeert Uytterhoeven 		priv->esc_seq.buf[priv->esc_seq.len++] = c;
5418c483758SRobert Abel 		priv->esc_seq.buf[priv->esc_seq.len] = '\0';
54239f8ea46SGeert Uytterhoeven 	} else {
54339f8ea46SGeert Uytterhoeven 		/* aborts any previous escape sequence */
54439f8ea46SGeert Uytterhoeven 		priv->esc_seq.len = -1;
54539f8ea46SGeert Uytterhoeven 
54639f8ea46SGeert Uytterhoeven 		switch (c) {
54739f8ea46SGeert Uytterhoeven 		case LCD_ESCAPE_CHAR:
54839f8ea46SGeert Uytterhoeven 			/* start of an escape sequence */
54939f8ea46SGeert Uytterhoeven 			priv->esc_seq.len = 0;
5508c483758SRobert Abel 			priv->esc_seq.buf[priv->esc_seq.len] = '\0';
55139f8ea46SGeert Uytterhoeven 			break;
55239f8ea46SGeert Uytterhoeven 		case '\b':
55339f8ea46SGeert Uytterhoeven 			/* go back one char and clear it */
55411588b59SLars Poeschel 			if (lcd->addr.x > 0) {
55539f8ea46SGeert Uytterhoeven 				/*
55639f8ea46SGeert Uytterhoeven 				 * check if we're not at the
55739f8ea46SGeert Uytterhoeven 				 * end of the line
55839f8ea46SGeert Uytterhoeven 				 */
55911588b59SLars Poeschel 				if (lcd->addr.x < hdc->bwidth)
56039f8ea46SGeert Uytterhoeven 					/* back one char */
5612c6a82f2SLars Poeschel 					hdc->write_cmd(hdc, LCD_CMD_SHIFT);
56211588b59SLars Poeschel 				lcd->addr.x--;
56339f8ea46SGeert Uytterhoeven 			}
56439f8ea46SGeert Uytterhoeven 			/* replace with a space */
56571ff701bSLars Poeschel 			hdc->write_data(hdc, ' ');
56639f8ea46SGeert Uytterhoeven 			/* back one char again */
5672c6a82f2SLars Poeschel 			hdc->write_cmd(hdc, LCD_CMD_SHIFT);
56839f8ea46SGeert Uytterhoeven 			break;
5699629cccaSRobert Abel 		case '\f':
57039f8ea46SGeert Uytterhoeven 			/* quickly clear the display */
57139f8ea46SGeert Uytterhoeven 			charlcd_clear_fast(lcd);
57239f8ea46SGeert Uytterhoeven 			break;
57339f8ea46SGeert Uytterhoeven 		case '\n':
57439f8ea46SGeert Uytterhoeven 			/*
57539f8ea46SGeert Uytterhoeven 			 * flush the remainder of the current line and
57639f8ea46SGeert Uytterhoeven 			 * go to the beginning of the next line
57739f8ea46SGeert Uytterhoeven 			 */
57811588b59SLars Poeschel 			for (; lcd->addr.x < hdc->bwidth; lcd->addr.x++)
579b26deabbSLars Poeschel 				lcd->ops->print(lcd, ' ');
580b26deabbSLars Poeschel 
58111588b59SLars Poeschel 			lcd->addr.x = 0;
58211588b59SLars Poeschel 			lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
583d3a2fb81SLars Poeschel 			lcd->ops->gotoxy(lcd);
58439f8ea46SGeert Uytterhoeven 			break;
58539f8ea46SGeert Uytterhoeven 		case '\r':
58639f8ea46SGeert Uytterhoeven 			/* go to the beginning of the same line */
58711588b59SLars Poeschel 			lcd->addr.x = 0;
588d3a2fb81SLars Poeschel 			lcd->ops->gotoxy(lcd);
58939f8ea46SGeert Uytterhoeven 			break;
59039f8ea46SGeert Uytterhoeven 		case '\t':
59139f8ea46SGeert Uytterhoeven 			/* print a space instead of the tab */
59239f8ea46SGeert Uytterhoeven 			charlcd_print(lcd, ' ');
59339f8ea46SGeert Uytterhoeven 			break;
59439f8ea46SGeert Uytterhoeven 		default:
59539f8ea46SGeert Uytterhoeven 			/* simply print this char */
59639f8ea46SGeert Uytterhoeven 			charlcd_print(lcd, c);
59739f8ea46SGeert Uytterhoeven 			break;
59839f8ea46SGeert Uytterhoeven 		}
59939f8ea46SGeert Uytterhoeven 	}
60039f8ea46SGeert Uytterhoeven 
60139f8ea46SGeert Uytterhoeven 	/*
60239f8ea46SGeert Uytterhoeven 	 * now we'll see if we're in an escape mode and if the current
60339f8ea46SGeert Uytterhoeven 	 * escape sequence can be understood.
60439f8ea46SGeert Uytterhoeven 	 */
60539f8ea46SGeert Uytterhoeven 	if (priv->esc_seq.len >= 2) {
60639f8ea46SGeert Uytterhoeven 		int processed = 0;
60739f8ea46SGeert Uytterhoeven 
60839f8ea46SGeert Uytterhoeven 		if (!strcmp(priv->esc_seq.buf, "[2J")) {
60939f8ea46SGeert Uytterhoeven 			/* clear the display */
61039f8ea46SGeert Uytterhoeven 			charlcd_clear_fast(lcd);
61139f8ea46SGeert Uytterhoeven 			processed = 1;
61239f8ea46SGeert Uytterhoeven 		} else if (!strcmp(priv->esc_seq.buf, "[H")) {
61339f8ea46SGeert Uytterhoeven 			/* cursor to home */
61439f8ea46SGeert Uytterhoeven 			charlcd_home(lcd);
61539f8ea46SGeert Uytterhoeven 			processed = 1;
61639f8ea46SGeert Uytterhoeven 		}
61739f8ea46SGeert Uytterhoeven 		/* codes starting with ^[[L */
61839f8ea46SGeert Uytterhoeven 		else if ((priv->esc_seq.len >= 3) &&
61939f8ea46SGeert Uytterhoeven 			 (priv->esc_seq.buf[0] == '[') &&
62039f8ea46SGeert Uytterhoeven 			 (priv->esc_seq.buf[1] == 'L')) {
62139f8ea46SGeert Uytterhoeven 			processed = handle_lcd_special_code(lcd);
62239f8ea46SGeert Uytterhoeven 		}
62339f8ea46SGeert Uytterhoeven 
62439f8ea46SGeert Uytterhoeven 		/* LCD special escape codes */
62539f8ea46SGeert Uytterhoeven 		/*
62639f8ea46SGeert Uytterhoeven 		 * flush the escape sequence if it's been processed
62739f8ea46SGeert Uytterhoeven 		 * or if it is getting too long.
62839f8ea46SGeert Uytterhoeven 		 */
62939f8ea46SGeert Uytterhoeven 		if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
63039f8ea46SGeert Uytterhoeven 			priv->esc_seq.len = -1;
63139f8ea46SGeert Uytterhoeven 	} /* escape codes */
63239f8ea46SGeert Uytterhoeven }
63339f8ea46SGeert Uytterhoeven 
63439f8ea46SGeert Uytterhoeven static struct charlcd *the_charlcd;
63539f8ea46SGeert Uytterhoeven 
63639f8ea46SGeert Uytterhoeven static ssize_t charlcd_write(struct file *file, const char __user *buf,
63739f8ea46SGeert Uytterhoeven 			     size_t count, loff_t *ppos)
63839f8ea46SGeert Uytterhoeven {
63939f8ea46SGeert Uytterhoeven 	const char __user *tmp = buf;
64039f8ea46SGeert Uytterhoeven 	char c;
64139f8ea46SGeert Uytterhoeven 
64239f8ea46SGeert Uytterhoeven 	for (; count-- > 0; (*ppos)++, tmp++) {
64339f8ea46SGeert Uytterhoeven 		if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
64439f8ea46SGeert Uytterhoeven 			/*
64539f8ea46SGeert Uytterhoeven 			 * let's be a little nice with other processes
64639f8ea46SGeert Uytterhoeven 			 * that need some CPU
64739f8ea46SGeert Uytterhoeven 			 */
64839f8ea46SGeert Uytterhoeven 			schedule();
64939f8ea46SGeert Uytterhoeven 
65039f8ea46SGeert Uytterhoeven 		if (get_user(c, tmp))
65139f8ea46SGeert Uytterhoeven 			return -EFAULT;
65239f8ea46SGeert Uytterhoeven 
65339f8ea46SGeert Uytterhoeven 		charlcd_write_char(the_charlcd, c);
65439f8ea46SGeert Uytterhoeven 	}
65539f8ea46SGeert Uytterhoeven 
65639f8ea46SGeert Uytterhoeven 	return tmp - buf;
65739f8ea46SGeert Uytterhoeven }
65839f8ea46SGeert Uytterhoeven 
65939f8ea46SGeert Uytterhoeven static int charlcd_open(struct inode *inode, struct file *file)
66039f8ea46SGeert Uytterhoeven {
661b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
66293dc1774SWilly Tarreau 	int ret;
66339f8ea46SGeert Uytterhoeven 
66493dc1774SWilly Tarreau 	ret = -EBUSY;
66539f8ea46SGeert Uytterhoeven 	if (!atomic_dec_and_test(&charlcd_available))
66693dc1774SWilly Tarreau 		goto fail;	/* open only once at a time */
66739f8ea46SGeert Uytterhoeven 
66893dc1774SWilly Tarreau 	ret = -EPERM;
66939f8ea46SGeert Uytterhoeven 	if (file->f_mode & FMODE_READ)	/* device is write-only */
67093dc1774SWilly Tarreau 		goto fail;
67139f8ea46SGeert Uytterhoeven 
67239f8ea46SGeert Uytterhoeven 	if (priv->must_clear) {
67339f8ea46SGeert Uytterhoeven 		charlcd_clear_display(&priv->lcd);
67439f8ea46SGeert Uytterhoeven 		priv->must_clear = false;
67539f8ea46SGeert Uytterhoeven 	}
67639f8ea46SGeert Uytterhoeven 	return nonseekable_open(inode, file);
67793dc1774SWilly Tarreau 
67893dc1774SWilly Tarreau  fail:
67993dc1774SWilly Tarreau 	atomic_inc(&charlcd_available);
68093dc1774SWilly Tarreau 	return ret;
68139f8ea46SGeert Uytterhoeven }
68239f8ea46SGeert Uytterhoeven 
68339f8ea46SGeert Uytterhoeven static int charlcd_release(struct inode *inode, struct file *file)
68439f8ea46SGeert Uytterhoeven {
68539f8ea46SGeert Uytterhoeven 	atomic_inc(&charlcd_available);
68639f8ea46SGeert Uytterhoeven 	return 0;
68739f8ea46SGeert Uytterhoeven }
68839f8ea46SGeert Uytterhoeven 
68939f8ea46SGeert Uytterhoeven static const struct file_operations charlcd_fops = {
69039f8ea46SGeert Uytterhoeven 	.write   = charlcd_write,
69139f8ea46SGeert Uytterhoeven 	.open    = charlcd_open,
69239f8ea46SGeert Uytterhoeven 	.release = charlcd_release,
69339f8ea46SGeert Uytterhoeven 	.llseek  = no_llseek,
69439f8ea46SGeert Uytterhoeven };
69539f8ea46SGeert Uytterhoeven 
69639f8ea46SGeert Uytterhoeven static struct miscdevice charlcd_dev = {
69739f8ea46SGeert Uytterhoeven 	.minor	= LCD_MINOR,
69839f8ea46SGeert Uytterhoeven 	.name	= "lcd",
69939f8ea46SGeert Uytterhoeven 	.fops	= &charlcd_fops,
70039f8ea46SGeert Uytterhoeven };
70139f8ea46SGeert Uytterhoeven 
70239f8ea46SGeert Uytterhoeven static void charlcd_puts(struct charlcd *lcd, const char *s)
70339f8ea46SGeert Uytterhoeven {
70439f8ea46SGeert Uytterhoeven 	const char *tmp = s;
70539f8ea46SGeert Uytterhoeven 	int count = strlen(s);
70639f8ea46SGeert Uytterhoeven 
70739f8ea46SGeert Uytterhoeven 	for (; count-- > 0; tmp++) {
70839f8ea46SGeert Uytterhoeven 		if (!in_interrupt() && (((count + 1) & 0x1f) == 0))
70939f8ea46SGeert Uytterhoeven 			/*
71039f8ea46SGeert Uytterhoeven 			 * let's be a little nice with other processes
71139f8ea46SGeert Uytterhoeven 			 * that need some CPU
71239f8ea46SGeert Uytterhoeven 			 */
71339f8ea46SGeert Uytterhoeven 			schedule();
71439f8ea46SGeert Uytterhoeven 
71539f8ea46SGeert Uytterhoeven 		charlcd_write_char(lcd, *tmp);
71639f8ea46SGeert Uytterhoeven 	}
71739f8ea46SGeert Uytterhoeven }
71839f8ea46SGeert Uytterhoeven 
719c9171722SMans Rullgard #ifdef CONFIG_PANEL_BOOT_MESSAGE
720c9171722SMans Rullgard #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
721c9171722SMans Rullgard #else
722c9171722SMans Rullgard #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
723c9171722SMans Rullgard #endif
724c9171722SMans Rullgard 
725cc5d04d8SMans Rullgard #ifdef CONFIG_CHARLCD_BL_ON
726cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L+"
727cc5d04d8SMans Rullgard #elif defined(CONFIG_CHARLCD_BL_FLASH)
728cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L*"
729cc5d04d8SMans Rullgard #else
730cc5d04d8SMans Rullgard #define LCD_INIT_BL "\x1b[L-"
731cc5d04d8SMans Rullgard #endif
732cc5d04d8SMans Rullgard 
73339f8ea46SGeert Uytterhoeven /* initialize the LCD driver */
73439f8ea46SGeert Uytterhoeven static int charlcd_init(struct charlcd *lcd)
73539f8ea46SGeert Uytterhoeven {
736b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
73739f8ea46SGeert Uytterhoeven 	int ret;
73839f8ea46SGeert Uytterhoeven 
73939f8ea46SGeert Uytterhoeven 	if (lcd->ops->backlight) {
74039f8ea46SGeert Uytterhoeven 		mutex_init(&priv->bl_tempo_lock);
74139f8ea46SGeert Uytterhoeven 		INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
74239f8ea46SGeert Uytterhoeven 	}
74339f8ea46SGeert Uytterhoeven 
74439f8ea46SGeert Uytterhoeven 	/*
74539f8ea46SGeert Uytterhoeven 	 * before this line, we must NOT send anything to the display.
74639f8ea46SGeert Uytterhoeven 	 * Since charlcd_init_display() needs to write data, we have to
74739f8ea46SGeert Uytterhoeven 	 * enable mark the LCD initialized just before.
74839f8ea46SGeert Uytterhoeven 	 */
74939f8ea46SGeert Uytterhoeven 	ret = charlcd_init_display(lcd);
75039f8ea46SGeert Uytterhoeven 	if (ret)
75139f8ea46SGeert Uytterhoeven 		return ret;
75239f8ea46SGeert Uytterhoeven 
75339f8ea46SGeert Uytterhoeven 	/* display a short message */
754cc5d04d8SMans Rullgard 	charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
755c9171722SMans Rullgard 
75639f8ea46SGeert Uytterhoeven 	/* clear the display on the next device opening */
75739f8ea46SGeert Uytterhoeven 	priv->must_clear = true;
75839f8ea46SGeert Uytterhoeven 	charlcd_home(lcd);
75939f8ea46SGeert Uytterhoeven 	return 0;
76039f8ea46SGeert Uytterhoeven }
76139f8ea46SGeert Uytterhoeven 
7622545c1c9SLars Poeschel struct charlcd *charlcd_alloc(void)
76339f8ea46SGeert Uytterhoeven {
76439f8ea46SGeert Uytterhoeven 	struct charlcd_priv *priv;
76539f8ea46SGeert Uytterhoeven 	struct charlcd *lcd;
76639f8ea46SGeert Uytterhoeven 
7672545c1c9SLars Poeschel 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
76839f8ea46SGeert Uytterhoeven 	if (!priv)
76939f8ea46SGeert Uytterhoeven 		return NULL;
77039f8ea46SGeert Uytterhoeven 
77139f8ea46SGeert Uytterhoeven 	priv->esc_seq.len = -1;
77239f8ea46SGeert Uytterhoeven 
77339f8ea46SGeert Uytterhoeven 	lcd = &priv->lcd;
77439f8ea46SGeert Uytterhoeven 
77539f8ea46SGeert Uytterhoeven 	return lcd;
77639f8ea46SGeert Uytterhoeven }
77739f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_alloc);
77839f8ea46SGeert Uytterhoeven 
7798e44fc85SAndy Shevchenko void charlcd_free(struct charlcd *lcd)
7808e44fc85SAndy Shevchenko {
7818e44fc85SAndy Shevchenko 	kfree(charlcd_to_priv(lcd));
7828e44fc85SAndy Shevchenko }
7838e44fc85SAndy Shevchenko EXPORT_SYMBOL_GPL(charlcd_free);
7848e44fc85SAndy Shevchenko 
78539f8ea46SGeert Uytterhoeven static int panel_notify_sys(struct notifier_block *this, unsigned long code,
78639f8ea46SGeert Uytterhoeven 			    void *unused)
78739f8ea46SGeert Uytterhoeven {
78839f8ea46SGeert Uytterhoeven 	struct charlcd *lcd = the_charlcd;
78939f8ea46SGeert Uytterhoeven 
79039f8ea46SGeert Uytterhoeven 	switch (code) {
79139f8ea46SGeert Uytterhoeven 	case SYS_DOWN:
79239f8ea46SGeert Uytterhoeven 		charlcd_puts(lcd,
79339f8ea46SGeert Uytterhoeven 			     "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
79439f8ea46SGeert Uytterhoeven 		break;
79539f8ea46SGeert Uytterhoeven 	case SYS_HALT:
79639f8ea46SGeert Uytterhoeven 		charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
79739f8ea46SGeert Uytterhoeven 		break;
79839f8ea46SGeert Uytterhoeven 	case SYS_POWER_OFF:
79939f8ea46SGeert Uytterhoeven 		charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
80039f8ea46SGeert Uytterhoeven 		break;
80139f8ea46SGeert Uytterhoeven 	default:
80239f8ea46SGeert Uytterhoeven 		break;
80339f8ea46SGeert Uytterhoeven 	}
80439f8ea46SGeert Uytterhoeven 	return NOTIFY_DONE;
80539f8ea46SGeert Uytterhoeven }
80639f8ea46SGeert Uytterhoeven 
80739f8ea46SGeert Uytterhoeven static struct notifier_block panel_notifier = {
80839f8ea46SGeert Uytterhoeven 	panel_notify_sys,
80939f8ea46SGeert Uytterhoeven 	NULL,
81039f8ea46SGeert Uytterhoeven 	0
81139f8ea46SGeert Uytterhoeven };
81239f8ea46SGeert Uytterhoeven 
81339f8ea46SGeert Uytterhoeven int charlcd_register(struct charlcd *lcd)
81439f8ea46SGeert Uytterhoeven {
81539f8ea46SGeert Uytterhoeven 	int ret;
81639f8ea46SGeert Uytterhoeven 
81739f8ea46SGeert Uytterhoeven 	ret = charlcd_init(lcd);
81839f8ea46SGeert Uytterhoeven 	if (ret)
81939f8ea46SGeert Uytterhoeven 		return ret;
82039f8ea46SGeert Uytterhoeven 
82139f8ea46SGeert Uytterhoeven 	ret = misc_register(&charlcd_dev);
82239f8ea46SGeert Uytterhoeven 	if (ret)
82339f8ea46SGeert Uytterhoeven 		return ret;
82439f8ea46SGeert Uytterhoeven 
82539f8ea46SGeert Uytterhoeven 	the_charlcd = lcd;
82639f8ea46SGeert Uytterhoeven 	register_reboot_notifier(&panel_notifier);
82739f8ea46SGeert Uytterhoeven 	return 0;
82839f8ea46SGeert Uytterhoeven }
82939f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_register);
83039f8ea46SGeert Uytterhoeven 
83139f8ea46SGeert Uytterhoeven int charlcd_unregister(struct charlcd *lcd)
83239f8ea46SGeert Uytterhoeven {
833b658a211SAndy Shevchenko 	struct charlcd_priv *priv = charlcd_to_priv(lcd);
83439f8ea46SGeert Uytterhoeven 
83539f8ea46SGeert Uytterhoeven 	unregister_reboot_notifier(&panel_notifier);
83639f8ea46SGeert Uytterhoeven 	charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
83739f8ea46SGeert Uytterhoeven 	misc_deregister(&charlcd_dev);
83839f8ea46SGeert Uytterhoeven 	the_charlcd = NULL;
83939f8ea46SGeert Uytterhoeven 	if (lcd->ops->backlight) {
84039f8ea46SGeert Uytterhoeven 		cancel_delayed_work_sync(&priv->bl_work);
84139f8ea46SGeert Uytterhoeven 		priv->lcd.ops->backlight(&priv->lcd, 0);
84239f8ea46SGeert Uytterhoeven 	}
84339f8ea46SGeert Uytterhoeven 
84439f8ea46SGeert Uytterhoeven 	return 0;
84539f8ea46SGeert Uytterhoeven }
84639f8ea46SGeert Uytterhoeven EXPORT_SYMBOL_GPL(charlcd_unregister);
84739f8ea46SGeert Uytterhoeven 
84839f8ea46SGeert Uytterhoeven MODULE_LICENSE("GPL");
849