brintos

brintos / linux-shallow public Read only

0
0
Text · 15.2 KiB · 19b6193 Raw
684 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Character LCD driver for Linux4 *5 * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>6 * Copyright (C) 2016-2017 Glider bvba7 */8 9#include <linux/atomic.h>10#include <linux/ctype.h>11#include <linux/fs.h>12#include <linux/miscdevice.h>13#include <linux/module.h>14#include <linux/notifier.h>15#include <linux/reboot.h>16#include <linux/slab.h>17#include <linux/uaccess.h>18#include <linux/workqueue.h>19 20#ifndef CONFIG_PANEL_BOOT_MESSAGE21#include <generated/utsrelease.h>22#endif23 24#include "charlcd.h"25 26/* Keep the backlight on this many seconds for each flash */27#define LCD_BL_TEMPO_PERIOD	428 29#define LCD_ESCAPE_LEN		24	/* Max chars for LCD escape command */30#define LCD_ESCAPE_CHAR		27	/* Use char 27 for escape command */31 32struct charlcd_priv {33	struct charlcd lcd;34 35	struct delayed_work bl_work;36	struct mutex bl_tempo_lock;	/* Protects access to bl_tempo */37	bool bl_tempo;38 39	bool must_clear;40 41	/* contains the LCD config state */42	unsigned long flags;43 44	/* Current escape sequence and it's length or -1 if outside */45	struct {46		char buf[LCD_ESCAPE_LEN + 1];47		int len;48	} esc_seq;49 50	unsigned long long drvdata[];51};52 53#define charlcd_to_priv(p)	container_of(p, struct charlcd_priv, lcd)54 55/* Device single-open policy control */56static atomic_t charlcd_available = ATOMIC_INIT(1);57 58/* turn the backlight on or off */59void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)60{61	struct charlcd_priv *priv = charlcd_to_priv(lcd);62 63	if (!lcd->ops->backlight)64		return;65 66	mutex_lock(&priv->bl_tempo_lock);67	if (!priv->bl_tempo)68		lcd->ops->backlight(lcd, on);69	mutex_unlock(&priv->bl_tempo_lock);70}71EXPORT_SYMBOL_GPL(charlcd_backlight);72 73static void charlcd_bl_off(struct work_struct *work)74{75	struct delayed_work *dwork = to_delayed_work(work);76	struct charlcd_priv *priv =77		container_of(dwork, struct charlcd_priv, bl_work);78 79	mutex_lock(&priv->bl_tempo_lock);80	if (priv->bl_tempo) {81		priv->bl_tempo = false;82		if (!(priv->flags & LCD_FLAG_L))83			priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);84	}85	mutex_unlock(&priv->bl_tempo_lock);86}87 88/* turn the backlight on for a little while */89void charlcd_poke(struct charlcd *lcd)90{91	struct charlcd_priv *priv = charlcd_to_priv(lcd);92 93	if (!lcd->ops->backlight)94		return;95 96	cancel_delayed_work_sync(&priv->bl_work);97 98	mutex_lock(&priv->bl_tempo_lock);99	if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))100		lcd->ops->backlight(lcd, CHARLCD_ON);101	priv->bl_tempo = true;102	schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);103	mutex_unlock(&priv->bl_tempo_lock);104}105EXPORT_SYMBOL_GPL(charlcd_poke);106 107static void charlcd_home(struct charlcd *lcd)108{109	lcd->addr.x = 0;110	lcd->addr.y = 0;111	lcd->ops->home(lcd);112}113 114static void charlcd_print(struct charlcd *lcd, char c)115{116	if (lcd->addr.x >= lcd->width)117		return;118 119	if (lcd->char_conv)120		c = lcd->char_conv[(unsigned char)c];121 122	if (!lcd->ops->print(lcd, c))123		lcd->addr.x++;124 125	/* prevents the cursor from wrapping onto the next line */126	if (lcd->addr.x == lcd->width)127		lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);128}129 130static void charlcd_clear_display(struct charlcd *lcd)131{132	lcd->ops->clear_display(lcd);133	lcd->addr.x = 0;134	lcd->addr.y = 0;135}136 137/*138 * Parses a movement command of the form "(.*);", where the group can be139 * any number of subcommands of the form "(x|y)[0-9]+".140 *141 * Returns whether the command is valid. The position arguments are142 * only written if the parsing was successful.143 *144 * For instance:145 *   - ";"          returns (<original x>, <original y>).146 *   - "x1;"        returns (1, <original y>).147 *   - "y2x1;"      returns (1, 2).148 *   - "x12y34x56;" returns (56, 34).149 *   - ""           fails.150 *   - "x"          fails.151 *   - "x;"         fails.152 *   - "x1"         fails.153 *   - "xy12;"      fails.154 *   - "x12yy12;"   fails.155 *   - "xx"         fails.156 */157static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)158{159	unsigned long new_x = *x;160	unsigned long new_y = *y;161	char *p;162 163	for (;;) {164		if (!*s)165			return false;166 167		if (*s == ';')168			break;169 170		if (*s == 'x') {171			new_x = simple_strtoul(s + 1, &p, 10);172			if (p == s + 1)173				return false;174			s = p;175		} else if (*s == 'y') {176			new_y = simple_strtoul(s + 1, &p, 10);177			if (p == s + 1)178				return false;179			s = p;180		} else {181			return false;182		}183	}184 185	*x = new_x;186	*y = new_y;187	return true;188}189 190/*191 * These are the file operation function for user access to /dev/lcd192 * This function can also be called from inside the kernel, by193 * setting file and ppos to NULL.194 *195 */196 197static inline int handle_lcd_special_code(struct charlcd *lcd)198{199	struct charlcd_priv *priv = charlcd_to_priv(lcd);200 201	/* LCD special codes */202 203	int processed = 0;204 205	char *esc = priv->esc_seq.buf + 2;206	int oldflags = priv->flags;207 208	/* check for display mode flags */209	switch (*esc) {210	case 'D':	/* Display ON */211		priv->flags |= LCD_FLAG_D;212		if (priv->flags != oldflags)213			lcd->ops->display(lcd, CHARLCD_ON);214 215		processed = 1;216		break;217	case 'd':	/* Display OFF */218		priv->flags &= ~LCD_FLAG_D;219		if (priv->flags != oldflags)220			lcd->ops->display(lcd, CHARLCD_OFF);221 222		processed = 1;223		break;224	case 'C':	/* Cursor ON */225		priv->flags |= LCD_FLAG_C;226		if (priv->flags != oldflags)227			lcd->ops->cursor(lcd, CHARLCD_ON);228 229		processed = 1;230		break;231	case 'c':	/* Cursor OFF */232		priv->flags &= ~LCD_FLAG_C;233		if (priv->flags != oldflags)234			lcd->ops->cursor(lcd, CHARLCD_OFF);235 236		processed = 1;237		break;238	case 'B':	/* Blink ON */239		priv->flags |= LCD_FLAG_B;240		if (priv->flags != oldflags)241			lcd->ops->blink(lcd, CHARLCD_ON);242 243		processed = 1;244		break;245	case 'b':	/* Blink OFF */246		priv->flags &= ~LCD_FLAG_B;247		if (priv->flags != oldflags)248			lcd->ops->blink(lcd, CHARLCD_OFF);249 250		processed = 1;251		break;252	case '+':	/* Back light ON */253		priv->flags |= LCD_FLAG_L;254		if (priv->flags != oldflags)255			charlcd_backlight(lcd, CHARLCD_ON);256 257		processed = 1;258		break;259	case '-':	/* Back light OFF */260		priv->flags &= ~LCD_FLAG_L;261		if (priv->flags != oldflags)262			charlcd_backlight(lcd, CHARLCD_OFF);263 264		processed = 1;265		break;266	case '*':	/* Flash back light */267		charlcd_poke(lcd);268		processed = 1;269		break;270	case 'f':	/* Small Font */271		priv->flags &= ~LCD_FLAG_F;272		if (priv->flags != oldflags)273			lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);274 275		processed = 1;276		break;277	case 'F':	/* Large Font */278		priv->flags |= LCD_FLAG_F;279		if (priv->flags != oldflags)280			lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);281 282		processed = 1;283		break;284	case 'n':	/* One Line */285		priv->flags &= ~LCD_FLAG_N;286		if (priv->flags != oldflags)287			lcd->ops->lines(lcd, CHARLCD_LINES_1);288 289		processed = 1;290		break;291	case 'N':	/* Two Lines */292		priv->flags |= LCD_FLAG_N;293		if (priv->flags != oldflags)294			lcd->ops->lines(lcd, CHARLCD_LINES_2);295 296		processed = 1;297		break;298	case 'l':	/* Shift Cursor Left */299		if (lcd->addr.x > 0) {300			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))301				lcd->addr.x--;302		}303 304		processed = 1;305		break;306	case 'r':	/* shift cursor right */307		if (lcd->addr.x < lcd->width) {308			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))309				lcd->addr.x++;310		}311 312		processed = 1;313		break;314	case 'L':	/* shift display left */315		lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);316		processed = 1;317		break;318	case 'R':	/* shift display right */319		lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);320		processed = 1;321		break;322	case 'k': {	/* kill end of line */323		int x, xs, ys;324 325		xs = lcd->addr.x;326		ys = lcd->addr.y;327		for (x = lcd->addr.x; x < lcd->width; x++)328			lcd->ops->print(lcd, ' ');329 330		/* restore cursor position */331		lcd->addr.x = xs;332		lcd->addr.y = ys;333		lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);334		processed = 1;335		break;336	}337	case 'I':	/* reinitialize display */338		lcd->ops->init_display(lcd);339		priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |340			LCD_FLAG_C | LCD_FLAG_B;341		processed = 1;342		break;343	case 'G':344		if (lcd->ops->redefine_char)345			processed = lcd->ops->redefine_char(lcd, esc);346		else347			processed = 1;348		break;349 350	case 'x':	/* gotoxy : LxXXX[yYYY]; */351	case 'y':	/* gotoxy : LyYYY[xXXX]; */352		if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')353			break;354 355		/* If the command is valid, move to the new address */356		if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))357			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);358 359		/* Regardless of its validity, mark as processed */360		processed = 1;361		break;362	}363 364	return processed;365}366 367static void charlcd_write_char(struct charlcd *lcd, char c)368{369	struct charlcd_priv *priv = charlcd_to_priv(lcd);370 371	/* first, we'll test if we're in escape mode */372	if ((c != '\n') && priv->esc_seq.len >= 0) {373		/* yes, let's add this char to the buffer */374		priv->esc_seq.buf[priv->esc_seq.len++] = c;375		priv->esc_seq.buf[priv->esc_seq.len] = '\0';376	} else {377		/* aborts any previous escape sequence */378		priv->esc_seq.len = -1;379 380		switch (c) {381		case LCD_ESCAPE_CHAR:382			/* start of an escape sequence */383			priv->esc_seq.len = 0;384			priv->esc_seq.buf[priv->esc_seq.len] = '\0';385			break;386		case '\b':387			/* go back one char and clear it */388			if (lcd->addr.x > 0) {389				/* back one char */390				if (!lcd->ops->shift_cursor(lcd,391							CHARLCD_SHIFT_LEFT))392					lcd->addr.x--;393			}394			/* replace with a space */395			charlcd_print(lcd, ' ');396			/* back one char again */397			if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))398				lcd->addr.x--;399 400			break;401		case '\f':402			/* quickly clear the display */403			charlcd_clear_display(lcd);404			break;405		case '\n':406			/*407			 * flush the remainder of the current line and408			 * go to the beginning of the next line409			 */410			for (; lcd->addr.x < lcd->width; lcd->addr.x++)411				lcd->ops->print(lcd, ' ');412 413			lcd->addr.x = 0;414			lcd->addr.y = (lcd->addr.y + 1) % lcd->height;415			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);416			break;417		case '\r':418			/* go to the beginning of the same line */419			lcd->addr.x = 0;420			lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);421			break;422		case '\t':423			/* print a space instead of the tab */424			charlcd_print(lcd, ' ');425			break;426		default:427			/* simply print this char */428			charlcd_print(lcd, c);429			break;430		}431	}432 433	/*434	 * now we'll see if we're in an escape mode and if the current435	 * escape sequence can be understood.436	 */437	if (priv->esc_seq.len >= 2) {438		int processed = 0;439 440		if (!strcmp(priv->esc_seq.buf, "[2J")) {441			/* clear the display */442			charlcd_clear_display(lcd);443			processed = 1;444		} else if (!strcmp(priv->esc_seq.buf, "[H")) {445			/* cursor to home */446			charlcd_home(lcd);447			processed = 1;448		}449		/* codes starting with ^[[L */450		else if ((priv->esc_seq.len >= 3) &&451			 (priv->esc_seq.buf[0] == '[') &&452			 (priv->esc_seq.buf[1] == 'L')) {453			processed = handle_lcd_special_code(lcd);454		}455 456		/* LCD special escape codes */457		/*458		 * flush the escape sequence if it's been processed459		 * or if it is getting too long.460		 */461		if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))462			priv->esc_seq.len = -1;463	} /* escape codes */464}465 466static struct charlcd *the_charlcd;467 468static ssize_t charlcd_write(struct file *file, const char __user *buf,469			     size_t count, loff_t *ppos)470{471	const char __user *tmp = buf;472	char c;473 474	for (; count-- > 0; (*ppos)++, tmp++) {475		if (((count + 1) & 0x1f) == 0) {476			/*477			 * charlcd_write() is invoked as a VFS->write() callback478			 * and as such it is always invoked from preemptible479			 * context and may sleep.480			 */481			cond_resched();482		}483 484		if (get_user(c, tmp))485			return -EFAULT;486 487		charlcd_write_char(the_charlcd, c);488	}489 490	return tmp - buf;491}492 493static int charlcd_open(struct inode *inode, struct file *file)494{495	struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);496	int ret;497 498	ret = -EBUSY;499	if (!atomic_dec_and_test(&charlcd_available))500		goto fail;	/* open only once at a time */501 502	ret = -EPERM;503	if (file->f_mode & FMODE_READ)	/* device is write-only */504		goto fail;505 506	if (priv->must_clear) {507		priv->lcd.ops->clear_display(&priv->lcd);508		priv->must_clear = false;509		priv->lcd.addr.x = 0;510		priv->lcd.addr.y = 0;511	}512	return nonseekable_open(inode, file);513 514 fail:515	atomic_inc(&charlcd_available);516	return ret;517}518 519static int charlcd_release(struct inode *inode, struct file *file)520{521	atomic_inc(&charlcd_available);522	return 0;523}524 525static const struct file_operations charlcd_fops = {526	.write   = charlcd_write,527	.open    = charlcd_open,528	.release = charlcd_release,529};530 531static struct miscdevice charlcd_dev = {532	.minor	= LCD_MINOR,533	.name	= "lcd",534	.fops	= &charlcd_fops,535};536 537static void charlcd_puts(struct charlcd *lcd, const char *s)538{539	const char *tmp = s;540	int count = strlen(s);541 542	for (; count-- > 0; tmp++) {543		if (((count + 1) & 0x1f) == 0)544			cond_resched();545 546		charlcd_write_char(lcd, *tmp);547	}548}549 550#ifdef CONFIG_PANEL_BOOT_MESSAGE551#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE552#else553#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"554#endif555 556#ifdef CONFIG_CHARLCD_BL_ON557#define LCD_INIT_BL "\x1b[L+"558#elif defined(CONFIG_CHARLCD_BL_FLASH)559#define LCD_INIT_BL "\x1b[L*"560#else561#define LCD_INIT_BL "\x1b[L-"562#endif563 564/* initialize the LCD driver */565static int charlcd_init(struct charlcd *lcd)566{567	struct charlcd_priv *priv = charlcd_to_priv(lcd);568	int ret;569 570	priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |571		      LCD_FLAG_C | LCD_FLAG_B;572	if (lcd->ops->backlight) {573		mutex_init(&priv->bl_tempo_lock);574		INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);575	}576 577	/*578	 * before this line, we must NOT send anything to the display.579	 * Since charlcd_init_display() needs to write data, we have to580	 * enable mark the LCD initialized just before.581	 */582	if (WARN_ON(!lcd->ops->init_display))583		return -EINVAL;584 585	ret = lcd->ops->init_display(lcd);586	if (ret)587		return ret;588 589	/* display a short message */590	charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);591 592	/* clear the display on the next device opening */593	priv->must_clear = true;594	charlcd_home(lcd);595	return 0;596}597 598struct charlcd *charlcd_alloc(void)599{600	struct charlcd_priv *priv;601	struct charlcd *lcd;602 603	priv = kzalloc(sizeof(*priv), GFP_KERNEL);604	if (!priv)605		return NULL;606 607	priv->esc_seq.len = -1;608 609	lcd = &priv->lcd;610 611	return lcd;612}613EXPORT_SYMBOL_GPL(charlcd_alloc);614 615void charlcd_free(struct charlcd *lcd)616{617	kfree(charlcd_to_priv(lcd));618}619EXPORT_SYMBOL_GPL(charlcd_free);620 621static int panel_notify_sys(struct notifier_block *this, unsigned long code,622			    void *unused)623{624	struct charlcd *lcd = the_charlcd;625 626	switch (code) {627	case SYS_DOWN:628		charlcd_puts(lcd,629			     "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");630		break;631	case SYS_HALT:632		charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");633		break;634	case SYS_POWER_OFF:635		charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");636		break;637	default:638		break;639	}640	return NOTIFY_DONE;641}642 643static struct notifier_block panel_notifier = {644	.notifier_call = panel_notify_sys,645};646 647int charlcd_register(struct charlcd *lcd)648{649	int ret;650 651	ret = charlcd_init(lcd);652	if (ret)653		return ret;654 655	ret = misc_register(&charlcd_dev);656	if (ret)657		return ret;658 659	the_charlcd = lcd;660	register_reboot_notifier(&panel_notifier);661	return 0;662}663EXPORT_SYMBOL_GPL(charlcd_register);664 665int charlcd_unregister(struct charlcd *lcd)666{667	struct charlcd_priv *priv = charlcd_to_priv(lcd);668 669	unregister_reboot_notifier(&panel_notifier);670	charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");671	misc_deregister(&charlcd_dev);672	the_charlcd = NULL;673	if (lcd->ops->backlight) {674		cancel_delayed_work_sync(&priv->bl_work);675		priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);676	}677 678	return 0;679}680EXPORT_SYMBOL_GPL(charlcd_unregister);681 682MODULE_DESCRIPTION("Character LCD core support");683MODULE_LICENSE("GPL");684