charlcd.c 15 KB

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