Commit | Line | Data |
---|---|---|
351f683b | 1 | // SPDX-License-Identifier: GPL-2.0+ |
39f8ea46 GU |
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 | |
39f8ea46 GU |
7 | */ |
8 | ||
9 | #include <linux/atomic.h> | |
b34050fa | 10 | #include <linux/ctype.h> |
39f8ea46 GU |
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 | #include <generated/utsrelease.h> | |
21 | ||
75354284 | 22 | #include "charlcd.h" |
2545c1c9 | 23 | #include "hd44780_common.h" |
39f8ea46 GU |
24 | |
25 | /* Keep the backlight on this many seconds for each flash */ | |
26 | #define LCD_BL_TEMPO_PERIOD 4 | |
27 | ||
39f8ea46 | 28 | /* LCD commands */ |
39f8ea46 GU |
29 | #define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */ |
30 | #define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */ | |
31 | #define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */ | |
32 | #define LCD_CMD_BLINK_ON 0x01 /* Set blink on */ | |
33 | ||
39f8ea46 GU |
34 | #define LCD_CMD_FUNCTION_SET 0x20 /* Set function */ |
35 | #define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */ | |
36 | #define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */ | |
37 | #define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */ | |
38 | ||
39 | #define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */ | |
40 | ||
d3a2fb81 | 41 | #define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */ |
39f8ea46 GU |
42 | |
43 | #define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */ | |
44 | #define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */ | |
45 | ||
46 | struct charlcd_priv { | |
47 | struct charlcd lcd; | |
48 | ||
49 | struct delayed_work bl_work; | |
50 | struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ | |
51 | bool bl_tempo; | |
52 | ||
53 | bool must_clear; | |
54 | ||
55 | /* contains the LCD config state */ | |
56 | unsigned long int flags; | |
57 | ||
39f8ea46 GU |
58 | /* Current escape sequence and it's length or -1 if outside */ |
59 | struct { | |
60 | char buf[LCD_ESCAPE_LEN + 1]; | |
61 | int len; | |
62 | } esc_seq; | |
63 | ||
2f920c0f | 64 | unsigned long long drvdata[]; |
39f8ea46 GU |
65 | }; |
66 | ||
b658a211 | 67 | #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd) |
39f8ea46 GU |
68 | |
69 | /* Device single-open policy control */ | |
70 | static atomic_t charlcd_available = ATOMIC_INIT(1); | |
71 | ||
39f8ea46 | 72 | /* turn the backlight on or off */ |
2bf82b5a | 73 | void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on) |
39f8ea46 | 74 | { |
b658a211 | 75 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
76 | |
77 | if (!lcd->ops->backlight) | |
78 | return; | |
79 | ||
80 | mutex_lock(&priv->bl_tempo_lock); | |
81 | if (!priv->bl_tempo) | |
82 | lcd->ops->backlight(lcd, on); | |
83 | mutex_unlock(&priv->bl_tempo_lock); | |
84 | } | |
2bf82b5a | 85 | EXPORT_SYMBOL_GPL(charlcd_backlight); |
39f8ea46 GU |
86 | |
87 | static void charlcd_bl_off(struct work_struct *work) | |
88 | { | |
89 | struct delayed_work *dwork = to_delayed_work(work); | |
90 | struct charlcd_priv *priv = | |
91 | container_of(dwork, struct charlcd_priv, bl_work); | |
92 | ||
93 | mutex_lock(&priv->bl_tempo_lock); | |
94 | if (priv->bl_tempo) { | |
95 | priv->bl_tempo = false; | |
96 | if (!(priv->flags & LCD_FLAG_L)) | |
bd26b181 | 97 | priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); |
39f8ea46 GU |
98 | } |
99 | mutex_unlock(&priv->bl_tempo_lock); | |
100 | } | |
101 | ||
102 | /* turn the backlight on for a little while */ | |
103 | void charlcd_poke(struct charlcd *lcd) | |
104 | { | |
b658a211 | 105 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
106 | |
107 | if (!lcd->ops->backlight) | |
108 | return; | |
109 | ||
110 | cancel_delayed_work_sync(&priv->bl_work); | |
111 | ||
112 | mutex_lock(&priv->bl_tempo_lock); | |
113 | if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) | |
bd26b181 | 114 | lcd->ops->backlight(lcd, CHARLCD_ON); |
39f8ea46 GU |
115 | priv->bl_tempo = true; |
116 | schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ); | |
117 | mutex_unlock(&priv->bl_tempo_lock); | |
118 | } | |
119 | EXPORT_SYMBOL_GPL(charlcd_poke); | |
120 | ||
39f8ea46 GU |
121 | static void charlcd_home(struct charlcd *lcd) |
122 | { | |
11588b59 LP |
123 | lcd->addr.x = 0; |
124 | lcd->addr.y = 0; | |
88645a86 | 125 | lcd->ops->home(lcd); |
39f8ea46 GU |
126 | } |
127 | ||
128 | static void charlcd_print(struct charlcd *lcd, char c) | |
129 | { | |
d3a2fb81 LP |
130 | struct hd44780_common *hdc = lcd->drvdata; |
131 | ||
b26deabb LP |
132 | if (lcd->char_conv) |
133 | c = lcd->char_conv[(unsigned char)c]; | |
39f8ea46 | 134 | |
b26deabb | 135 | if (!lcd->ops->print(lcd, c)) |
11588b59 | 136 | lcd->addr.x++; |
54bc937f | 137 | |
b26deabb | 138 | /* prevents the cursor from wrapping onto the next line */ |
d3a2fb81 LP |
139 | if (lcd->addr.x == hdc->bwidth) |
140 | lcd->ops->gotoxy(lcd); | |
39f8ea46 GU |
141 | } |
142 | ||
143 | static void charlcd_clear_fast(struct charlcd *lcd) | |
144 | { | |
2545c1c9 | 145 | struct hd44780_common *hdc = lcd->drvdata; |
39f8ea46 GU |
146 | int pos; |
147 | ||
148 | charlcd_home(lcd); | |
149 | ||
150 | if (lcd->ops->clear_fast) | |
151 | lcd->ops->clear_fast(lcd); | |
152 | else | |
2545c1c9 | 153 | for (pos = 0; pos < min(2, lcd->height) * hdc->hwidth; pos++) |
b26deabb | 154 | lcd->ops->print(lcd, ' '); |
39f8ea46 GU |
155 | |
156 | charlcd_home(lcd); | |
157 | } | |
158 | ||
b34050fa MO |
159 | /* |
160 | * Parses a movement command of the form "(.*);", where the group can be | |
161 | * any number of subcommands of the form "(x|y)[0-9]+". | |
162 | * | |
163 | * Returns whether the command is valid. The position arguments are | |
164 | * only written if the parsing was successful. | |
165 | * | |
166 | * For instance: | |
167 | * - ";" returns (<original x>, <original y>). | |
168 | * - "x1;" returns (1, <original y>). | |
169 | * - "y2x1;" returns (1, 2). | |
170 | * - "x12y34x56;" returns (56, 34). | |
171 | * - "" fails. | |
172 | * - "x" fails. | |
173 | * - "x;" fails. | |
174 | * - "x1" fails. | |
175 | * - "xy12;" fails. | |
176 | * - "x12yy12;" fails. | |
177 | * - "xx" fails. | |
178 | */ | |
179 | static bool parse_xy(const char *s, unsigned long *x, unsigned long *y) | |
180 | { | |
181 | unsigned long new_x = *x; | |
182 | unsigned long new_y = *y; | |
d717e7da | 183 | char *p; |
b34050fa MO |
184 | |
185 | for (;;) { | |
186 | if (!*s) | |
187 | return false; | |
188 | ||
189 | if (*s == ';') | |
190 | break; | |
191 | ||
192 | if (*s == 'x') { | |
d717e7da AS |
193 | new_x = simple_strtoul(s + 1, &p, 10); |
194 | if (p == s + 1) | |
b34050fa | 195 | return false; |
d717e7da | 196 | s = p; |
b34050fa | 197 | } else if (*s == 'y') { |
d717e7da AS |
198 | new_y = simple_strtoul(s + 1, &p, 10); |
199 | if (p == s + 1) | |
b34050fa | 200 | return false; |
d717e7da | 201 | s = p; |
b34050fa MO |
202 | } else { |
203 | return false; | |
204 | } | |
205 | } | |
206 | ||
207 | *x = new_x; | |
208 | *y = new_y; | |
209 | return true; | |
210 | } | |
211 | ||
39f8ea46 GU |
212 | /* |
213 | * These are the file operation function for user access to /dev/lcd | |
214 | * This function can also be called from inside the kernel, by | |
215 | * setting file and ppos to NULL. | |
216 | * | |
217 | */ | |
218 | ||
219 | static inline int handle_lcd_special_code(struct charlcd *lcd) | |
220 | { | |
b658a211 | 221 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
2545c1c9 | 222 | struct hd44780_common *hdc = lcd->drvdata; |
39f8ea46 GU |
223 | |
224 | /* LCD special codes */ | |
225 | ||
226 | int processed = 0; | |
227 | ||
228 | char *esc = priv->esc_seq.buf + 2; | |
229 | int oldflags = priv->flags; | |
230 | ||
231 | /* check for display mode flags */ | |
232 | switch (*esc) { | |
233 | case 'D': /* Display ON */ | |
234 | priv->flags |= LCD_FLAG_D; | |
d2f2187e LP |
235 | if (priv->flags != oldflags) |
236 | lcd->ops->display(lcd, CHARLCD_ON); | |
237 | ||
39f8ea46 GU |
238 | processed = 1; |
239 | break; | |
240 | case 'd': /* Display OFF */ | |
241 | priv->flags &= ~LCD_FLAG_D; | |
d2f2187e LP |
242 | if (priv->flags != oldflags) |
243 | lcd->ops->display(lcd, CHARLCD_OFF); | |
244 | ||
39f8ea46 GU |
245 | processed = 1; |
246 | break; | |
247 | case 'C': /* Cursor ON */ | |
248 | priv->flags |= LCD_FLAG_C; | |
d2f2187e LP |
249 | if (priv->flags != oldflags) |
250 | lcd->ops->cursor(lcd, CHARLCD_ON); | |
251 | ||
39f8ea46 GU |
252 | processed = 1; |
253 | break; | |
254 | case 'c': /* Cursor OFF */ | |
255 | priv->flags &= ~LCD_FLAG_C; | |
d2f2187e LP |
256 | if (priv->flags != oldflags) |
257 | lcd->ops->cursor(lcd, CHARLCD_OFF); | |
258 | ||
39f8ea46 GU |
259 | processed = 1; |
260 | break; | |
261 | case 'B': /* Blink ON */ | |
262 | priv->flags |= LCD_FLAG_B; | |
d2f2187e LP |
263 | if (priv->flags != oldflags) |
264 | lcd->ops->blink(lcd, CHARLCD_ON); | |
265 | ||
39f8ea46 GU |
266 | processed = 1; |
267 | break; | |
268 | case 'b': /* Blink OFF */ | |
269 | priv->flags &= ~LCD_FLAG_B; | |
d2f2187e LP |
270 | if (priv->flags != oldflags) |
271 | lcd->ops->blink(lcd, CHARLCD_OFF); | |
272 | ||
39f8ea46 GU |
273 | processed = 1; |
274 | break; | |
275 | case '+': /* Back light ON */ | |
276 | priv->flags |= LCD_FLAG_L; | |
277 | processed = 1; | |
278 | break; | |
279 | case '-': /* Back light OFF */ | |
280 | priv->flags &= ~LCD_FLAG_L; | |
281 | processed = 1; | |
282 | break; | |
283 | case '*': /* Flash back light */ | |
284 | charlcd_poke(lcd); | |
285 | processed = 1; | |
286 | break; | |
287 | case 'f': /* Small Font */ | |
288 | priv->flags &= ~LCD_FLAG_F; | |
d2f2187e LP |
289 | if (priv->flags != oldflags) |
290 | lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL); | |
291 | ||
39f8ea46 GU |
292 | processed = 1; |
293 | break; | |
294 | case 'F': /* Large Font */ | |
295 | priv->flags |= LCD_FLAG_F; | |
d2f2187e LP |
296 | if (priv->flags != oldflags) |
297 | lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE); | |
298 | ||
39f8ea46 GU |
299 | processed = 1; |
300 | break; | |
301 | case 'n': /* One Line */ | |
302 | priv->flags &= ~LCD_FLAG_N; | |
d2f2187e LP |
303 | if (priv->flags != oldflags) |
304 | lcd->ops->lines(lcd, CHARLCD_LINES_1); | |
305 | ||
39f8ea46 GU |
306 | processed = 1; |
307 | break; | |
308 | case 'N': /* Two Lines */ | |
309 | priv->flags |= LCD_FLAG_N; | |
d2f2187e LP |
310 | if (priv->flags != oldflags) |
311 | lcd->ops->lines(lcd, CHARLCD_LINES_2); | |
312 | ||
99b9b490 | 313 | processed = 1; |
39f8ea46 GU |
314 | break; |
315 | case 'l': /* Shift Cursor Left */ | |
11588b59 | 316 | if (lcd->addr.x > 0) { |
d2f2187e LP |
317 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) |
318 | lcd->addr.x--; | |
39f8ea46 | 319 | } |
d2f2187e | 320 | |
39f8ea46 GU |
321 | processed = 1; |
322 | break; | |
323 | case 'r': /* shift cursor right */ | |
11588b59 | 324 | if (lcd->addr.x < lcd->width) { |
d2f2187e LP |
325 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT)) |
326 | lcd->addr.x++; | |
39f8ea46 | 327 | } |
d2f2187e | 328 | |
39f8ea46 GU |
329 | processed = 1; |
330 | break; | |
331 | case 'L': /* shift display left */ | |
d2f2187e | 332 | lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT); |
39f8ea46 GU |
333 | processed = 1; |
334 | break; | |
335 | case 'R': /* shift display right */ | |
d2f2187e | 336 | lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT); |
39f8ea46 GU |
337 | processed = 1; |
338 | break; | |
339 | case 'k': { /* kill end of line */ | |
b26deabb | 340 | int x, xs, ys; |
39f8ea46 | 341 | |
b26deabb LP |
342 | xs = lcd->addr.x; |
343 | ys = lcd->addr.y; | |
11588b59 | 344 | for (x = lcd->addr.x; x < hdc->bwidth; x++) |
b26deabb | 345 | lcd->ops->print(lcd, ' '); |
39f8ea46 GU |
346 | |
347 | /* restore cursor position */ | |
b26deabb LP |
348 | lcd->addr.x = xs; |
349 | lcd->addr.y = ys; | |
d3a2fb81 | 350 | lcd->ops->gotoxy(lcd); |
39f8ea46 GU |
351 | processed = 1; |
352 | break; | |
353 | } | |
354 | case 'I': /* reinitialize display */ | |
01ec46df LP |
355 | lcd->ops->init_display(lcd); |
356 | priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | | |
357 | LCD_FLAG_C | LCD_FLAG_B; | |
39f8ea46 GU |
358 | processed = 1; |
359 | break; | |
360 | case 'G': { | |
361 | /* Generator : LGcxxxxx...xx; must have <c> between '0' | |
362 | * and '7', representing the numerical ASCII code of the | |
363 | * redefined character, and <xx...xx> a sequence of 16 | |
364 | * hex digits representing 8 bytes for each character. | |
365 | * Most LCDs will only use 5 lower bits of the 7 first | |
366 | * bytes. | |
367 | */ | |
368 | ||
369 | unsigned char cgbytes[8]; | |
370 | unsigned char cgaddr; | |
371 | int cgoffset; | |
372 | int shift; | |
373 | char value; | |
374 | int addr; | |
375 | ||
376 | if (!strchr(esc, ';')) | |
377 | break; | |
378 | ||
379 | esc++; | |
380 | ||
381 | cgaddr = *(esc++) - '0'; | |
382 | if (cgaddr > 7) { | |
383 | processed = 1; | |
384 | break; | |
385 | } | |
386 | ||
387 | cgoffset = 0; | |
388 | shift = 0; | |
389 | value = 0; | |
390 | while (*esc && cgoffset < 8) { | |
3f03b649 AS |
391 | int half; |
392 | ||
39f8ea46 | 393 | shift ^= 4; |
3f03b649 AS |
394 | |
395 | half = hex_to_bin(*esc++); | |
396 | if (half < 0) | |
39f8ea46 | 397 | continue; |
39f8ea46 | 398 | |
3f03b649 | 399 | value |= half << shift; |
39f8ea46 GU |
400 | if (shift == 0) { |
401 | cgbytes[cgoffset++] = value; | |
402 | value = 0; | |
403 | } | |
39f8ea46 GU |
404 | } |
405 | ||
2c6a82f2 | 406 | hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8)); |
39f8ea46 | 407 | for (addr = 0; addr < cgoffset; addr++) |
71ff701b | 408 | hdc->write_data(hdc, cgbytes[addr]); |
39f8ea46 GU |
409 | |
410 | /* ensures that we stop writing to CGRAM */ | |
d3a2fb81 | 411 | lcd->ops->gotoxy(lcd); |
39f8ea46 GU |
412 | processed = 1; |
413 | break; | |
414 | } | |
415 | case 'x': /* gotoxy : LxXXX[yYYY]; */ | |
416 | case 'y': /* gotoxy : LyYYY[xXXX]; */ | |
9bc30ab8 MR |
417 | if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';') |
418 | break; | |
419 | ||
b34050fa | 420 | /* If the command is valid, move to the new address */ |
11588b59 | 421 | if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y)) |
d3a2fb81 | 422 | lcd->ops->gotoxy(lcd); |
39f8ea46 | 423 | |
b34050fa | 424 | /* Regardless of its validity, mark as processed */ |
39f8ea46 GU |
425 | processed = 1; |
426 | break; | |
427 | } | |
428 | ||
429 | /* TODO: This indent party here got ugly, clean it! */ | |
430 | /* Check whether one flag was changed */ | |
431 | if (oldflags == priv->flags) | |
432 | return processed; | |
433 | ||
434 | /* check whether one of B,C,D flags were changed */ | |
435 | if ((oldflags ^ priv->flags) & | |
436 | (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D)) | |
437 | /* set display mode */ | |
2c6a82f2 | 438 | hdc->write_cmd(hdc, |
39f8ea46 GU |
439 | LCD_CMD_DISPLAY_CTRL | |
440 | ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) | | |
441 | ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) | | |
442 | ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0)); | |
443 | /* check whether one of F,N flags was changed */ | |
444 | else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N)) | |
2c6a82f2 | 445 | hdc->write_cmd(hdc, |
ac201479 | 446 | LCD_CMD_FUNCTION_SET | |
3fc04dd7 | 447 | ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) | |
39f8ea46 GU |
448 | ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) | |
449 | ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0)); | |
450 | /* check whether L flag was changed */ | |
451 | else if ((oldflags ^ priv->flags) & LCD_FLAG_L) | |
452 | charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L)); | |
453 | ||
454 | return processed; | |
455 | } | |
456 | ||
457 | static void charlcd_write_char(struct charlcd *lcd, char c) | |
458 | { | |
b658a211 | 459 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
2545c1c9 | 460 | struct hd44780_common *hdc = lcd->drvdata; |
39f8ea46 GU |
461 | |
462 | /* first, we'll test if we're in escape mode */ | |
463 | if ((c != '\n') && priv->esc_seq.len >= 0) { | |
464 | /* yes, let's add this char to the buffer */ | |
465 | priv->esc_seq.buf[priv->esc_seq.len++] = c; | |
8c483758 | 466 | priv->esc_seq.buf[priv->esc_seq.len] = '\0'; |
39f8ea46 GU |
467 | } else { |
468 | /* aborts any previous escape sequence */ | |
469 | priv->esc_seq.len = -1; | |
470 | ||
471 | switch (c) { | |
472 | case LCD_ESCAPE_CHAR: | |
473 | /* start of an escape sequence */ | |
474 | priv->esc_seq.len = 0; | |
8c483758 | 475 | priv->esc_seq.buf[priv->esc_seq.len] = '\0'; |
39f8ea46 GU |
476 | break; |
477 | case '\b': | |
478 | /* go back one char and clear it */ | |
11588b59 | 479 | if (lcd->addr.x > 0) { |
d2f2187e LP |
480 | /* back one char */ |
481 | if (!lcd->ops->shift_cursor(lcd, | |
482 | CHARLCD_SHIFT_LEFT)) | |
483 | lcd->addr.x--; | |
39f8ea46 GU |
484 | } |
485 | /* replace with a space */ | |
d2f2187e | 486 | charlcd_print(lcd, ' '); |
39f8ea46 | 487 | /* back one char again */ |
d2f2187e LP |
488 | if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT)) |
489 | lcd->addr.x--; | |
490 | ||
39f8ea46 | 491 | break; |
9629ccca | 492 | case '\f': |
39f8ea46 GU |
493 | /* quickly clear the display */ |
494 | charlcd_clear_fast(lcd); | |
495 | break; | |
496 | case '\n': | |
497 | /* | |
498 | * flush the remainder of the current line and | |
499 | * go to the beginning of the next line | |
500 | */ | |
11588b59 | 501 | for (; lcd->addr.x < hdc->bwidth; lcd->addr.x++) |
b26deabb LP |
502 | lcd->ops->print(lcd, ' '); |
503 | ||
11588b59 LP |
504 | lcd->addr.x = 0; |
505 | lcd->addr.y = (lcd->addr.y + 1) % lcd->height; | |
d3a2fb81 | 506 | lcd->ops->gotoxy(lcd); |
39f8ea46 GU |
507 | break; |
508 | case '\r': | |
509 | /* go to the beginning of the same line */ | |
11588b59 | 510 | lcd->addr.x = 0; |
d3a2fb81 | 511 | lcd->ops->gotoxy(lcd); |
39f8ea46 GU |
512 | break; |
513 | case '\t': | |
514 | /* print a space instead of the tab */ | |
515 | charlcd_print(lcd, ' '); | |
516 | break; | |
517 | default: | |
518 | /* simply print this char */ | |
519 | charlcd_print(lcd, c); | |
520 | break; | |
521 | } | |
522 | } | |
523 | ||
524 | /* | |
525 | * now we'll see if we're in an escape mode and if the current | |
526 | * escape sequence can be understood. | |
527 | */ | |
528 | if (priv->esc_seq.len >= 2) { | |
529 | int processed = 0; | |
530 | ||
531 | if (!strcmp(priv->esc_seq.buf, "[2J")) { | |
532 | /* clear the display */ | |
533 | charlcd_clear_fast(lcd); | |
534 | processed = 1; | |
535 | } else if (!strcmp(priv->esc_seq.buf, "[H")) { | |
536 | /* cursor to home */ | |
537 | charlcd_home(lcd); | |
538 | processed = 1; | |
539 | } | |
540 | /* codes starting with ^[[L */ | |
541 | else if ((priv->esc_seq.len >= 3) && | |
542 | (priv->esc_seq.buf[0] == '[') && | |
543 | (priv->esc_seq.buf[1] == 'L')) { | |
544 | processed = handle_lcd_special_code(lcd); | |
545 | } | |
546 | ||
547 | /* LCD special escape codes */ | |
548 | /* | |
549 | * flush the escape sequence if it's been processed | |
550 | * or if it is getting too long. | |
551 | */ | |
552 | if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) | |
553 | priv->esc_seq.len = -1; | |
554 | } /* escape codes */ | |
555 | } | |
556 | ||
557 | static struct charlcd *the_charlcd; | |
558 | ||
559 | static ssize_t charlcd_write(struct file *file, const char __user *buf, | |
560 | size_t count, loff_t *ppos) | |
561 | { | |
562 | const char __user *tmp = buf; | |
563 | char c; | |
564 | ||
565 | for (; count-- > 0; (*ppos)++, tmp++) { | |
566 | if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) | |
567 | /* | |
568 | * let's be a little nice with other processes | |
569 | * that need some CPU | |
570 | */ | |
571 | schedule(); | |
572 | ||
573 | if (get_user(c, tmp)) | |
574 | return -EFAULT; | |
575 | ||
576 | charlcd_write_char(the_charlcd, c); | |
577 | } | |
578 | ||
579 | return tmp - buf; | |
580 | } | |
581 | ||
582 | static int charlcd_open(struct inode *inode, struct file *file) | |
583 | { | |
b658a211 | 584 | struct charlcd_priv *priv = charlcd_to_priv(the_charlcd); |
93dc1774 | 585 | int ret; |
39f8ea46 | 586 | |
93dc1774 | 587 | ret = -EBUSY; |
39f8ea46 | 588 | if (!atomic_dec_and_test(&charlcd_available)) |
93dc1774 | 589 | goto fail; /* open only once at a time */ |
39f8ea46 | 590 | |
93dc1774 | 591 | ret = -EPERM; |
39f8ea46 | 592 | if (file->f_mode & FMODE_READ) /* device is write-only */ |
93dc1774 | 593 | goto fail; |
39f8ea46 GU |
594 | |
595 | if (priv->must_clear) { | |
45421ffe | 596 | priv->lcd.ops->clear_display(&priv->lcd); |
39f8ea46 | 597 | priv->must_clear = false; |
45421ffe LP |
598 | priv->lcd.addr.x = 0; |
599 | priv->lcd.addr.y = 0; | |
39f8ea46 GU |
600 | } |
601 | return nonseekable_open(inode, file); | |
93dc1774 WT |
602 | |
603 | fail: | |
604 | atomic_inc(&charlcd_available); | |
605 | return ret; | |
39f8ea46 GU |
606 | } |
607 | ||
608 | static int charlcd_release(struct inode *inode, struct file *file) | |
609 | { | |
610 | atomic_inc(&charlcd_available); | |
611 | return 0; | |
612 | } | |
613 | ||
614 | static const struct file_operations charlcd_fops = { | |
615 | .write = charlcd_write, | |
616 | .open = charlcd_open, | |
617 | .release = charlcd_release, | |
618 | .llseek = no_llseek, | |
619 | }; | |
620 | ||
621 | static struct miscdevice charlcd_dev = { | |
622 | .minor = LCD_MINOR, | |
623 | .name = "lcd", | |
624 | .fops = &charlcd_fops, | |
625 | }; | |
626 | ||
627 | static void charlcd_puts(struct charlcd *lcd, const char *s) | |
628 | { | |
629 | const char *tmp = s; | |
630 | int count = strlen(s); | |
631 | ||
632 | for (; count-- > 0; tmp++) { | |
633 | if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) | |
634 | /* | |
635 | * let's be a little nice with other processes | |
636 | * that need some CPU | |
637 | */ | |
638 | schedule(); | |
639 | ||
640 | charlcd_write_char(lcd, *tmp); | |
641 | } | |
642 | } | |
643 | ||
c9171722 MR |
644 | #ifdef CONFIG_PANEL_BOOT_MESSAGE |
645 | #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE | |
646 | #else | |
647 | #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n" | |
648 | #endif | |
649 | ||
cc5d04d8 MR |
650 | #ifdef CONFIG_CHARLCD_BL_ON |
651 | #define LCD_INIT_BL "\x1b[L+" | |
652 | #elif defined(CONFIG_CHARLCD_BL_FLASH) | |
653 | #define LCD_INIT_BL "\x1b[L*" | |
654 | #else | |
655 | #define LCD_INIT_BL "\x1b[L-" | |
656 | #endif | |
657 | ||
39f8ea46 GU |
658 | /* initialize the LCD driver */ |
659 | static int charlcd_init(struct charlcd *lcd) | |
660 | { | |
b658a211 | 661 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
662 | int ret; |
663 | ||
01ec46df LP |
664 | priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D | |
665 | LCD_FLAG_C | LCD_FLAG_B; | |
39f8ea46 GU |
666 | if (lcd->ops->backlight) { |
667 | mutex_init(&priv->bl_tempo_lock); | |
668 | INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); | |
669 | } | |
670 | ||
671 | /* | |
672 | * before this line, we must NOT send anything to the display. | |
673 | * Since charlcd_init_display() needs to write data, we have to | |
674 | * enable mark the LCD initialized just before. | |
675 | */ | |
01ec46df | 676 | ret = lcd->ops->init_display(lcd); |
39f8ea46 GU |
677 | if (ret) |
678 | return ret; | |
679 | ||
680 | /* display a short message */ | |
cc5d04d8 | 681 | charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT); |
c9171722 | 682 | |
39f8ea46 GU |
683 | /* clear the display on the next device opening */ |
684 | priv->must_clear = true; | |
685 | charlcd_home(lcd); | |
686 | return 0; | |
687 | } | |
688 | ||
2545c1c9 | 689 | struct charlcd *charlcd_alloc(void) |
39f8ea46 GU |
690 | { |
691 | struct charlcd_priv *priv; | |
692 | struct charlcd *lcd; | |
693 | ||
2545c1c9 | 694 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
39f8ea46 GU |
695 | if (!priv) |
696 | return NULL; | |
697 | ||
698 | priv->esc_seq.len = -1; | |
699 | ||
700 | lcd = &priv->lcd; | |
39f8ea46 GU |
701 | |
702 | return lcd; | |
703 | } | |
704 | EXPORT_SYMBOL_GPL(charlcd_alloc); | |
705 | ||
8e44fc85 AS |
706 | void charlcd_free(struct charlcd *lcd) |
707 | { | |
708 | kfree(charlcd_to_priv(lcd)); | |
709 | } | |
710 | EXPORT_SYMBOL_GPL(charlcd_free); | |
711 | ||
39f8ea46 GU |
712 | static int panel_notify_sys(struct notifier_block *this, unsigned long code, |
713 | void *unused) | |
714 | { | |
715 | struct charlcd *lcd = the_charlcd; | |
716 | ||
717 | switch (code) { | |
718 | case SYS_DOWN: | |
719 | charlcd_puts(lcd, | |
720 | "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); | |
721 | break; | |
722 | case SYS_HALT: | |
723 | charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); | |
724 | break; | |
725 | case SYS_POWER_OFF: | |
726 | charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); | |
727 | break; | |
728 | default: | |
729 | break; | |
730 | } | |
731 | return NOTIFY_DONE; | |
732 | } | |
733 | ||
734 | static struct notifier_block panel_notifier = { | |
735 | panel_notify_sys, | |
736 | NULL, | |
737 | 0 | |
738 | }; | |
739 | ||
740 | int charlcd_register(struct charlcd *lcd) | |
741 | { | |
742 | int ret; | |
743 | ||
744 | ret = charlcd_init(lcd); | |
745 | if (ret) | |
746 | return ret; | |
747 | ||
748 | ret = misc_register(&charlcd_dev); | |
749 | if (ret) | |
750 | return ret; | |
751 | ||
752 | the_charlcd = lcd; | |
753 | register_reboot_notifier(&panel_notifier); | |
754 | return 0; | |
755 | } | |
756 | EXPORT_SYMBOL_GPL(charlcd_register); | |
757 | ||
758 | int charlcd_unregister(struct charlcd *lcd) | |
759 | { | |
b658a211 | 760 | struct charlcd_priv *priv = charlcd_to_priv(lcd); |
39f8ea46 GU |
761 | |
762 | unregister_reboot_notifier(&panel_notifier); | |
763 | charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); | |
764 | misc_deregister(&charlcd_dev); | |
765 | the_charlcd = NULL; | |
766 | if (lcd->ops->backlight) { | |
767 | cancel_delayed_work_sync(&priv->bl_work); | |
bd26b181 | 768 | priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF); |
39f8ea46 GU |
769 | } |
770 | ||
771 | return 0; | |
772 | } | |
773 | EXPORT_SYMBOL_GPL(charlcd_unregister); | |
774 | ||
775 | MODULE_LICENSE("GPL"); |