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