Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0+
  2/*
  3 * Character LCD driver for Linux
  4 *
  5 * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
  6 * Copyright (C) 2016-2017 Glider bvba
  7 */
  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_MESSAGE
 21#include <generated/utsrelease.h>
 22#endif
 23
 24#include "charlcd.h"
 25
 26/* Keep the backlight on this many seconds for each flash */
 27#define LCD_BL_TEMPO_PERIOD	4
 28
 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 be
139 * any number of subcommands of the form "(x|y)[0-9]+".
140 *
141 * Returns whether the command is valid. The position arguments are
142 * 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/lcd
192 * This function can also be called from inside the kernel, by
193 * 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		else
347			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 and
408			 * go to the beginning of the next line
409			 */
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 current
435	 * 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 processed
459		 * 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() callback
478			 * and as such it is always invoked from preemptible
479			 * 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_MESSAGE
551#define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
552#else
553#define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
554#endif
555
556#ifdef CONFIG_CHARLCD_BL_ON
557#define LCD_INIT_BL "\x1b[L+"
558#elif defined(CONFIG_CHARLCD_BL_FLASH)
559#define LCD_INIT_BL "\x1b[L*"
560#else
561#define LCD_INIT_BL "\x1b[L-"
562#endif
563
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 to
580	 * 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");