0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/atomic.h>
0010 #include <linux/ctype.h>
0011 #include <linux/fs.h>
0012 #include <linux/miscdevice.h>
0013 #include <linux/module.h>
0014 #include <linux/notifier.h>
0015 #include <linux/reboot.h>
0016 #include <linux/slab.h>
0017 #include <linux/uaccess.h>
0018 #include <linux/workqueue.h>
0019
0020 #include <generated/utsrelease.h>
0021
0022 #include "charlcd.h"
0023
0024
0025 #define LCD_BL_TEMPO_PERIOD 4
0026
0027 #define LCD_ESCAPE_LEN 24
0028 #define LCD_ESCAPE_CHAR 27
0029
0030 struct charlcd_priv {
0031 struct charlcd lcd;
0032
0033 struct delayed_work bl_work;
0034 struct mutex bl_tempo_lock;
0035 bool bl_tempo;
0036
0037 bool must_clear;
0038
0039
0040 unsigned long flags;
0041
0042
0043 struct {
0044 char buf[LCD_ESCAPE_LEN + 1];
0045 int len;
0046 } esc_seq;
0047
0048 unsigned long long drvdata[];
0049 };
0050
0051 #define charlcd_to_priv(p) container_of(p, struct charlcd_priv, lcd)
0052
0053
0054 static atomic_t charlcd_available = ATOMIC_INIT(1);
0055
0056
0057 void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
0058 {
0059 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0060
0061 if (!lcd->ops->backlight)
0062 return;
0063
0064 mutex_lock(&priv->bl_tempo_lock);
0065 if (!priv->bl_tempo)
0066 lcd->ops->backlight(lcd, on);
0067 mutex_unlock(&priv->bl_tempo_lock);
0068 }
0069 EXPORT_SYMBOL_GPL(charlcd_backlight);
0070
0071 static void charlcd_bl_off(struct work_struct *work)
0072 {
0073 struct delayed_work *dwork = to_delayed_work(work);
0074 struct charlcd_priv *priv =
0075 container_of(dwork, struct charlcd_priv, bl_work);
0076
0077 mutex_lock(&priv->bl_tempo_lock);
0078 if (priv->bl_tempo) {
0079 priv->bl_tempo = false;
0080 if (!(priv->flags & LCD_FLAG_L))
0081 priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
0082 }
0083 mutex_unlock(&priv->bl_tempo_lock);
0084 }
0085
0086
0087 void charlcd_poke(struct charlcd *lcd)
0088 {
0089 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0090
0091 if (!lcd->ops->backlight)
0092 return;
0093
0094 cancel_delayed_work_sync(&priv->bl_work);
0095
0096 mutex_lock(&priv->bl_tempo_lock);
0097 if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
0098 lcd->ops->backlight(lcd, CHARLCD_ON);
0099 priv->bl_tempo = true;
0100 schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
0101 mutex_unlock(&priv->bl_tempo_lock);
0102 }
0103 EXPORT_SYMBOL_GPL(charlcd_poke);
0104
0105 static void charlcd_home(struct charlcd *lcd)
0106 {
0107 lcd->addr.x = 0;
0108 lcd->addr.y = 0;
0109 lcd->ops->home(lcd);
0110 }
0111
0112 static void charlcd_print(struct charlcd *lcd, char c)
0113 {
0114 if (lcd->addr.x >= lcd->width)
0115 return;
0116
0117 if (lcd->char_conv)
0118 c = lcd->char_conv[(unsigned char)c];
0119
0120 if (!lcd->ops->print(lcd, c))
0121 lcd->addr.x++;
0122
0123
0124 if (lcd->addr.x == lcd->width)
0125 lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
0126 }
0127
0128 static void charlcd_clear_display(struct charlcd *lcd)
0129 {
0130 lcd->ops->clear_display(lcd);
0131 lcd->addr.x = 0;
0132 lcd->addr.y = 0;
0133 }
0134
0135
0136
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155 static bool parse_xy(const char *s, unsigned long *x, unsigned long *y)
0156 {
0157 unsigned long new_x = *x;
0158 unsigned long new_y = *y;
0159 char *p;
0160
0161 for (;;) {
0162 if (!*s)
0163 return false;
0164
0165 if (*s == ';')
0166 break;
0167
0168 if (*s == 'x') {
0169 new_x = simple_strtoul(s + 1, &p, 10);
0170 if (p == s + 1)
0171 return false;
0172 s = p;
0173 } else if (*s == 'y') {
0174 new_y = simple_strtoul(s + 1, &p, 10);
0175 if (p == s + 1)
0176 return false;
0177 s = p;
0178 } else {
0179 return false;
0180 }
0181 }
0182
0183 *x = new_x;
0184 *y = new_y;
0185 return true;
0186 }
0187
0188
0189
0190
0191
0192
0193
0194
0195 static inline int handle_lcd_special_code(struct charlcd *lcd)
0196 {
0197 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0198
0199
0200
0201 int processed = 0;
0202
0203 char *esc = priv->esc_seq.buf + 2;
0204 int oldflags = priv->flags;
0205
0206
0207 switch (*esc) {
0208 case 'D':
0209 priv->flags |= LCD_FLAG_D;
0210 if (priv->flags != oldflags)
0211 lcd->ops->display(lcd, CHARLCD_ON);
0212
0213 processed = 1;
0214 break;
0215 case 'd':
0216 priv->flags &= ~LCD_FLAG_D;
0217 if (priv->flags != oldflags)
0218 lcd->ops->display(lcd, CHARLCD_OFF);
0219
0220 processed = 1;
0221 break;
0222 case 'C':
0223 priv->flags |= LCD_FLAG_C;
0224 if (priv->flags != oldflags)
0225 lcd->ops->cursor(lcd, CHARLCD_ON);
0226
0227 processed = 1;
0228 break;
0229 case 'c':
0230 priv->flags &= ~LCD_FLAG_C;
0231 if (priv->flags != oldflags)
0232 lcd->ops->cursor(lcd, CHARLCD_OFF);
0233
0234 processed = 1;
0235 break;
0236 case 'B':
0237 priv->flags |= LCD_FLAG_B;
0238 if (priv->flags != oldflags)
0239 lcd->ops->blink(lcd, CHARLCD_ON);
0240
0241 processed = 1;
0242 break;
0243 case 'b':
0244 priv->flags &= ~LCD_FLAG_B;
0245 if (priv->flags != oldflags)
0246 lcd->ops->blink(lcd, CHARLCD_OFF);
0247
0248 processed = 1;
0249 break;
0250 case '+':
0251 priv->flags |= LCD_FLAG_L;
0252 if (priv->flags != oldflags)
0253 charlcd_backlight(lcd, CHARLCD_ON);
0254
0255 processed = 1;
0256 break;
0257 case '-':
0258 priv->flags &= ~LCD_FLAG_L;
0259 if (priv->flags != oldflags)
0260 charlcd_backlight(lcd, CHARLCD_OFF);
0261
0262 processed = 1;
0263 break;
0264 case '*':
0265 charlcd_poke(lcd);
0266 processed = 1;
0267 break;
0268 case 'f':
0269 priv->flags &= ~LCD_FLAG_F;
0270 if (priv->flags != oldflags)
0271 lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
0272
0273 processed = 1;
0274 break;
0275 case 'F':
0276 priv->flags |= LCD_FLAG_F;
0277 if (priv->flags != oldflags)
0278 lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
0279
0280 processed = 1;
0281 break;
0282 case 'n':
0283 priv->flags &= ~LCD_FLAG_N;
0284 if (priv->flags != oldflags)
0285 lcd->ops->lines(lcd, CHARLCD_LINES_1);
0286
0287 processed = 1;
0288 break;
0289 case 'N':
0290 priv->flags |= LCD_FLAG_N;
0291 if (priv->flags != oldflags)
0292 lcd->ops->lines(lcd, CHARLCD_LINES_2);
0293
0294 processed = 1;
0295 break;
0296 case 'l':
0297 if (lcd->addr.x > 0) {
0298 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
0299 lcd->addr.x--;
0300 }
0301
0302 processed = 1;
0303 break;
0304 case 'r':
0305 if (lcd->addr.x < lcd->width) {
0306 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
0307 lcd->addr.x++;
0308 }
0309
0310 processed = 1;
0311 break;
0312 case 'L':
0313 lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
0314 processed = 1;
0315 break;
0316 case 'R':
0317 lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
0318 processed = 1;
0319 break;
0320 case 'k': {
0321 int x, xs, ys;
0322
0323 xs = lcd->addr.x;
0324 ys = lcd->addr.y;
0325 for (x = lcd->addr.x; x < lcd->width; x++)
0326 lcd->ops->print(lcd, ' ');
0327
0328
0329 lcd->addr.x = xs;
0330 lcd->addr.y = ys;
0331 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
0332 processed = 1;
0333 break;
0334 }
0335 case 'I':
0336 lcd->ops->init_display(lcd);
0337 priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
0338 LCD_FLAG_C | LCD_FLAG_B;
0339 processed = 1;
0340 break;
0341 case 'G':
0342 if (lcd->ops->redefine_char)
0343 processed = lcd->ops->redefine_char(lcd, esc);
0344 else
0345 processed = 1;
0346 break;
0347
0348 case 'x':
0349 case 'y':
0350 if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
0351 break;
0352
0353
0354 if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
0355 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
0356
0357
0358 processed = 1;
0359 break;
0360 }
0361
0362 return processed;
0363 }
0364
0365 static void charlcd_write_char(struct charlcd *lcd, char c)
0366 {
0367 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0368
0369
0370 if ((c != '\n') && priv->esc_seq.len >= 0) {
0371
0372 priv->esc_seq.buf[priv->esc_seq.len++] = c;
0373 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
0374 } else {
0375
0376 priv->esc_seq.len = -1;
0377
0378 switch (c) {
0379 case LCD_ESCAPE_CHAR:
0380
0381 priv->esc_seq.len = 0;
0382 priv->esc_seq.buf[priv->esc_seq.len] = '\0';
0383 break;
0384 case '\b':
0385
0386 if (lcd->addr.x > 0) {
0387
0388 if (!lcd->ops->shift_cursor(lcd,
0389 CHARLCD_SHIFT_LEFT))
0390 lcd->addr.x--;
0391 }
0392
0393 charlcd_print(lcd, ' ');
0394
0395 if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
0396 lcd->addr.x--;
0397
0398 break;
0399 case '\f':
0400
0401 charlcd_clear_display(lcd);
0402 break;
0403 case '\n':
0404
0405
0406
0407
0408 for (; lcd->addr.x < lcd->width; lcd->addr.x++)
0409 lcd->ops->print(lcd, ' ');
0410
0411 lcd->addr.x = 0;
0412 lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
0413 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
0414 break;
0415 case '\r':
0416
0417 lcd->addr.x = 0;
0418 lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
0419 break;
0420 case '\t':
0421
0422 charlcd_print(lcd, ' ');
0423 break;
0424 default:
0425
0426 charlcd_print(lcd, c);
0427 break;
0428 }
0429 }
0430
0431
0432
0433
0434
0435 if (priv->esc_seq.len >= 2) {
0436 int processed = 0;
0437
0438 if (!strcmp(priv->esc_seq.buf, "[2J")) {
0439
0440 charlcd_clear_display(lcd);
0441 processed = 1;
0442 } else if (!strcmp(priv->esc_seq.buf, "[H")) {
0443
0444 charlcd_home(lcd);
0445 processed = 1;
0446 }
0447
0448 else if ((priv->esc_seq.len >= 3) &&
0449 (priv->esc_seq.buf[0] == '[') &&
0450 (priv->esc_seq.buf[1] == 'L')) {
0451 processed = handle_lcd_special_code(lcd);
0452 }
0453
0454
0455
0456
0457
0458
0459 if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN))
0460 priv->esc_seq.len = -1;
0461 }
0462 }
0463
0464 static struct charlcd *the_charlcd;
0465
0466 static ssize_t charlcd_write(struct file *file, const char __user *buf,
0467 size_t count, loff_t *ppos)
0468 {
0469 const char __user *tmp = buf;
0470 char c;
0471
0472 for (; count-- > 0; (*ppos)++, tmp++) {
0473 if (((count + 1) & 0x1f) == 0) {
0474
0475
0476
0477
0478
0479 cond_resched();
0480 }
0481
0482 if (get_user(c, tmp))
0483 return -EFAULT;
0484
0485 charlcd_write_char(the_charlcd, c);
0486 }
0487
0488 return tmp - buf;
0489 }
0490
0491 static int charlcd_open(struct inode *inode, struct file *file)
0492 {
0493 struct charlcd_priv *priv = charlcd_to_priv(the_charlcd);
0494 int ret;
0495
0496 ret = -EBUSY;
0497 if (!atomic_dec_and_test(&charlcd_available))
0498 goto fail;
0499
0500 ret = -EPERM;
0501 if (file->f_mode & FMODE_READ)
0502 goto fail;
0503
0504 if (priv->must_clear) {
0505 priv->lcd.ops->clear_display(&priv->lcd);
0506 priv->must_clear = false;
0507 priv->lcd.addr.x = 0;
0508 priv->lcd.addr.y = 0;
0509 }
0510 return nonseekable_open(inode, file);
0511
0512 fail:
0513 atomic_inc(&charlcd_available);
0514 return ret;
0515 }
0516
0517 static int charlcd_release(struct inode *inode, struct file *file)
0518 {
0519 atomic_inc(&charlcd_available);
0520 return 0;
0521 }
0522
0523 static const struct file_operations charlcd_fops = {
0524 .write = charlcd_write,
0525 .open = charlcd_open,
0526 .release = charlcd_release,
0527 .llseek = no_llseek,
0528 };
0529
0530 static struct miscdevice charlcd_dev = {
0531 .minor = LCD_MINOR,
0532 .name = "lcd",
0533 .fops = &charlcd_fops,
0534 };
0535
0536 static void charlcd_puts(struct charlcd *lcd, const char *s)
0537 {
0538 const char *tmp = s;
0539 int count = strlen(s);
0540
0541 for (; count-- > 0; tmp++) {
0542 if (((count + 1) & 0x1f) == 0)
0543 cond_resched();
0544
0545 charlcd_write_char(lcd, *tmp);
0546 }
0547 }
0548
0549 #ifdef CONFIG_PANEL_BOOT_MESSAGE
0550 #define LCD_INIT_TEXT CONFIG_PANEL_BOOT_MESSAGE
0551 #else
0552 #define LCD_INIT_TEXT "Linux-" UTS_RELEASE "\n"
0553 #endif
0554
0555 #ifdef CONFIG_CHARLCD_BL_ON
0556 #define LCD_INIT_BL "\x1b[L+"
0557 #elif defined(CONFIG_CHARLCD_BL_FLASH)
0558 #define LCD_INIT_BL "\x1b[L*"
0559 #else
0560 #define LCD_INIT_BL "\x1b[L-"
0561 #endif
0562
0563
0564 static int charlcd_init(struct charlcd *lcd)
0565 {
0566 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0567 int ret;
0568
0569 priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
0570 LCD_FLAG_C | LCD_FLAG_B;
0571 if (lcd->ops->backlight) {
0572 mutex_init(&priv->bl_tempo_lock);
0573 INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
0574 }
0575
0576
0577
0578
0579
0580
0581 if (WARN_ON(!lcd->ops->init_display))
0582 return -EINVAL;
0583
0584 ret = lcd->ops->init_display(lcd);
0585 if (ret)
0586 return ret;
0587
0588
0589 charlcd_puts(lcd, "\x1b[Lc\x1b[Lb" LCD_INIT_BL LCD_INIT_TEXT);
0590
0591
0592 priv->must_clear = true;
0593 charlcd_home(lcd);
0594 return 0;
0595 }
0596
0597 struct charlcd *charlcd_alloc(void)
0598 {
0599 struct charlcd_priv *priv;
0600 struct charlcd *lcd;
0601
0602 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
0603 if (!priv)
0604 return NULL;
0605
0606 priv->esc_seq.len = -1;
0607
0608 lcd = &priv->lcd;
0609
0610 return lcd;
0611 }
0612 EXPORT_SYMBOL_GPL(charlcd_alloc);
0613
0614 void charlcd_free(struct charlcd *lcd)
0615 {
0616 kfree(charlcd_to_priv(lcd));
0617 }
0618 EXPORT_SYMBOL_GPL(charlcd_free);
0619
0620 static int panel_notify_sys(struct notifier_block *this, unsigned long code,
0621 void *unused)
0622 {
0623 struct charlcd *lcd = the_charlcd;
0624
0625 switch (code) {
0626 case SYS_DOWN:
0627 charlcd_puts(lcd,
0628 "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+");
0629 break;
0630 case SYS_HALT:
0631 charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+");
0632 break;
0633 case SYS_POWER_OFF:
0634 charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+");
0635 break;
0636 default:
0637 break;
0638 }
0639 return NOTIFY_DONE;
0640 }
0641
0642 static struct notifier_block panel_notifier = {
0643 .notifier_call = panel_notify_sys,
0644 };
0645
0646 int charlcd_register(struct charlcd *lcd)
0647 {
0648 int ret;
0649
0650 ret = charlcd_init(lcd);
0651 if (ret)
0652 return ret;
0653
0654 ret = misc_register(&charlcd_dev);
0655 if (ret)
0656 return ret;
0657
0658 the_charlcd = lcd;
0659 register_reboot_notifier(&panel_notifier);
0660 return 0;
0661 }
0662 EXPORT_SYMBOL_GPL(charlcd_register);
0663
0664 int charlcd_unregister(struct charlcd *lcd)
0665 {
0666 struct charlcd_priv *priv = charlcd_to_priv(lcd);
0667
0668 unregister_reboot_notifier(&panel_notifier);
0669 charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-");
0670 misc_deregister(&charlcd_dev);
0671 the_charlcd = NULL;
0672 if (lcd->ops->backlight) {
0673 cancel_delayed_work_sync(&priv->bl_work);
0674 priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
0675 }
0676
0677 return 0;
0678 }
0679 EXPORT_SYMBOL_GPL(charlcd_unregister);
0680
0681 MODULE_LICENSE("GPL");