QNAP-TS419p system daemon
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

700 lines
17 KiB

  1. /***************************************************************************
  2. * Copyright (C) 05/2011 by Olaf Rempel *
  3. * razzor@kopf-tisch.de *
  4. * *
  5. * This program is free software; you can redistribute it and/or modify *
  6. * it under the terms of the GNU General Public License as published by *
  7. * the Free Software Foundation; either version 2 of the License, or *
  8. * (at your option) any later version. *
  9. * *
  10. * This program is distributed in the hope that it will be useful, *
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
  13. * GNU General Public License for more details. *
  14. * *
  15. * You should have received a copy of the GNU General Public License *
  16. * along with this program; if not, write to the *
  17. * Free Software Foundation, Inc., *
  18. * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
  19. ***************************************************************************/
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <unistd.h>
  23. #include <string.h>
  24. #include <sys/socket.h>
  25. #include <sys/types.h>
  26. #include <sys/stat.h>
  27. #include <fcntl.h>
  28. #include <termios.h>
  29. #include "event.h"
  30. #include "lcd.h"
  31. #include "list.h"
  32. #include "logging.h"
  33. #define _LCD_DEBUG 0
  34. #define _LCD_DUMMY 1
  35. #define LCD_SCROLL_SPEED 750 /* 750ms */
  36. #define LCD_RESET_TIMEOUT 250 /* 250ms */
  37. #define LCD_RESET_RETRY_TIMEOUT 10000 /* 10s */
  38. #define A125_CMD_GETBUTTON { 0x4D, 0x06 } // not tried
  39. #define A125_CMD_SETLINE { 0x4D, 0x0C, 0x00, 0x10 } // [2] is line, append 16 chars
  40. #define A125_CMD_CLEAR { 0x4D, 0x0D } // works, but slow
  41. #define A125_CMD_BACKLIGHT { 0x4D, 0x5E, 0x00 } // [2] is on/off
  42. #define A125_CMD_RESET { 0x4D, 0xFF }
  43. #define A125_EVENT_ID { 0x53, 0x01 } // never seen
  44. #define A125_EVENT_BUTTON1 { 0x53, 0x05, 0x00, 0x01 }
  45. #define A125_EVENT_BUTTON2 { 0x53, 0x05, 0x00, 0x02 }
  46. #define A125_EVENT_VERSION { 0x53, 0x08 } // never seen
  47. #define A125_EVENT_ACTINFO { 0x53, 0xAA }
  48. #define A125_EVENT_INVALID { 0x53, 0xFB } // never seen
  49. struct lcdpage {
  50. struct list_head list;
  51. int priority;
  52. int (*callback)(struct lcddev *dev, int event, void *privdata);
  53. void *privdata;
  54. };
  55. enum lcdstate {
  56. LCD_STATE_NOT_INITIALIZED = 0x00,
  57. LCD_STATE_INITIALIZING,
  58. LCD_STATE_INITIALIZING_FAILED,
  59. LCD_STATE_READY,
  60. };
  61. struct lcddev {
  62. int fd;
  63. #if (_LCD_DUMMY)
  64. struct event_fd *fakedevice_event;
  65. #endif /* (_LCD_DUMMY) */
  66. struct termios oldtio;
  67. enum lcdstate state;
  68. int backlight_enabled;
  69. int backlight_timeout_ms;
  70. struct list_head page_list;
  71. struct lcdpage *current_page;
  72. const char *line_data[2];
  73. int line_length[2];
  74. int scroll_pos;
  75. int update_ms;
  76. struct event_fd *read_event;
  77. struct event_timeout *reset_timeout;
  78. struct event_timeout *backlight_timeout;
  79. struct event_timeout *scroll_timeout;
  80. struct event_timeout *update_timeout;
  81. };
  82. static void lcd_pagecallback(struct lcddev *dev, int event);
  83. void lcd_close(struct lcddev *dev)
  84. {
  85. if (dev->current_page) {
  86. dev->current_page->callback(dev, LCDPAGE_EVENT_EXIT, dev->current_page->privdata);
  87. dev->current_page = NULL;
  88. }
  89. struct lcdpage *search, *tmp;
  90. list_for_each_entry_safe(search, tmp, &dev->page_list, list) {
  91. free(search);
  92. }
  93. if (dev->reset_timeout) {
  94. event_remove_timeout(dev->reset_timeout);
  95. dev->reset_timeout = NULL;
  96. }
  97. if (dev->backlight_timeout) {
  98. event_remove_timeout(dev->backlight_timeout);
  99. dev->backlight_timeout = NULL;
  100. }
  101. if (dev->update_timeout) {
  102. event_remove_timeout(dev->update_timeout);
  103. dev->update_timeout = NULL;
  104. }
  105. if (dev->scroll_timeout) {
  106. event_remove_timeout(dev->scroll_timeout);
  107. dev->scroll_timeout = NULL;
  108. }
  109. if (dev->read_event) {
  110. event_remove_fd(dev->read_event);
  111. dev->read_event = NULL;
  112. }
  113. #if (_LCD_DUMMY)
  114. if (dev->fakedevice_event != NULL) {
  115. int fd = event_get_fd(dev->fakedevice_event);
  116. event_remove_fd(dev->fakedevice_event);
  117. dev->fakedevice_event = NULL;
  118. close(fd);
  119. } else
  120. #endif /* (_LCD_DUMMY) */
  121. {
  122. tcsetattr(dev->fd, TCSANOW, &dev->oldtio);
  123. }
  124. close(dev->fd);
  125. free(dev);
  126. }
  127. static int lcd_realdevice_open(struct lcddev *dev, const char *device)
  128. {
  129. dev->fd = open(device, O_RDWR | O_NOCTTY);
  130. if (dev->fd < 0) {
  131. log_print(LOG_ERROR, "%s(): failed to open '%s'", __FUNCTION__, device);
  132. return -1;
  133. }
  134. if (fcntl(dev->fd, F_SETFD, FD_CLOEXEC) < 0) {
  135. log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__);
  136. close(dev->fd);
  137. return -1;
  138. }
  139. tcgetattr(dev->fd, &dev->oldtio);
  140. struct termios newtio;
  141. memset(&newtio, 0, sizeof(newtio));
  142. newtio.c_iflag |= IGNBRK;
  143. newtio.c_lflag &= ~(ISIG | ICANON | ECHO);
  144. newtio.c_cflag = B1200 | CS8 | CLOCAL | CREAD;
  145. newtio.c_cc[VMIN] = 1;
  146. newtio.c_cc[VTIME] = 0;
  147. cfsetospeed(&newtio, B1200);
  148. cfsetispeed(&newtio, B1200);
  149. int err = tcsetattr(dev->fd, TCSAFLUSH, &newtio);
  150. if (err < 0) {
  151. log_print(LOG_ERROR, "%s(): failed to set termios", __FUNCTION__);
  152. close(dev->fd);
  153. return -1;
  154. }
  155. return 0;
  156. }
  157. #if (_LCD_DUMMY)
  158. static int lcd_fakedevice_reply(int fd, void *privdata)
  159. {
  160. struct lcddev *dev = (struct lcddev *)privdata;
  161. char buf[32];
  162. if (read(fd, buf, sizeof(buf)) <= 0) {
  163. dev->fakedevice_event = NULL;
  164. return -1;
  165. }
  166. char reset_expect[] = A125_CMD_RESET;
  167. if (memcmp(buf, reset_expect, sizeof(reset_expect)) == 0) {
  168. char actinfo_cmd[] = A125_EVENT_ACTINFO;
  169. write(fd, actinfo_cmd, sizeof(actinfo_cmd));
  170. }
  171. return 0;
  172. }
  173. static int lcd_fakedevice_open(struct lcddev *dev)
  174. {
  175. int fd[2];
  176. if (socketpair(AF_LOCAL, SOCK_STREAM, 0 , fd) < 0) {
  177. log_print(LOG_ERROR, "%s(): socketpair() failed", __FUNCTION__);
  178. return -1;
  179. }
  180. if (fcntl(fd[0], F_SETFD, FD_CLOEXEC) < 0) {
  181. log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__);
  182. close(fd[0]);
  183. close(fd[1]);
  184. return -1;
  185. }
  186. if (fcntl(fd[1], F_SETFD, FD_CLOEXEC) < 0) {
  187. log_print(LOG_ERROR, "%s(): fcntl(FD_CLOEXEC)", __FUNCTION__);
  188. close(fd[0]);
  189. close(fd[1]);
  190. return -1;
  191. }
  192. dev->fd = fd[0];
  193. dev->fakedevice_event = event_add_readfd(NULL, fd[1], lcd_fakedevice_reply, dev);
  194. return 0;
  195. }
  196. #endif /* (_LCD_DUMMY) */
  197. #if (_LCD_DEBUG > 1)
  198. static void lcd_dump(const char *prefix, int len, int size, const char *data)
  199. {
  200. int i;
  201. int pos = 0;
  202. char buf[256];
  203. for (i = 0; i < len; i++) {
  204. pos += snprintf(buf, sizeof(buf) - pos, "0x%X ", (unsigned char)data[i]);
  205. }
  206. log_print(LOG_DEBUG, "%s:[%d/%d]: %s", prefix, len, size, buf);
  207. }
  208. #endif /* (_LCD_DEBUG > 1) */
  209. static int lcd_read(struct lcddev *dev, const char *buf, int len)
  210. {
  211. int retval = 0, cnt = 0;
  212. while (cnt < len) {
  213. retval = read(dev->fd, (char *)buf + cnt, len - cnt);
  214. if (retval <= 0)
  215. break;
  216. cnt += retval;
  217. }
  218. #if (_LCD_DEBUG > 1)
  219. lcd_dump(__FUNCTION__, cnt, len, buf);
  220. #endif /* (_LCD_DEBUG > 1) */
  221. return (cnt != 0) ? cnt : retval;
  222. }
  223. static int lcd_write(struct lcddev *dev, char *buf, int len)
  224. {
  225. int retval = write(dev->fd, buf, len);
  226. #if (_LCD_DEBUG > 1)
  227. lcd_dump(__FUNCTION__, retval, len, buf);
  228. #endif /* (_LCD_DEBUG > 1) */
  229. return retval;
  230. }
  231. static void lcd_reset(struct lcddev *dev);
  232. static int lcd_reset_retry_timeout_cb(int timerid, void *privdata)
  233. {
  234. struct lcddev *dev = (struct lcddev *)privdata;
  235. dev->reset_timeout = NULL;
  236. lcd_reset(dev);
  237. return -1; /* singleshot */
  238. }
  239. static int lcd_reset_timeout_cb(int timerid, void *privdata)
  240. {
  241. struct lcddev *dev = (struct lcddev *)privdata;
  242. log_print(LOG_ERROR, "failed to initalize LCD");
  243. dev->state = LCD_STATE_INITIALIZING_FAILED;
  244. dev->reset_timeout = event_add_timeout_ms(LCD_RESET_RETRY_TIMEOUT, lcd_reset_retry_timeout_cb, 0, dev);
  245. return -1; /* singleshot */
  246. }
  247. static void lcd_reset(struct lcddev *dev)
  248. {
  249. #if (_LCD_DEBUG > 0)
  250. log_print(LOG_DEBUG, "%s()", __FUNCTION__);
  251. #endif /* (_LCD_DEBUG > 0) */
  252. char cmd[] = A125_CMD_RESET;
  253. lcd_write(dev, cmd, sizeof(cmd));
  254. /* force next backlight command */
  255. dev->backlight_enabled = -1;
  256. dev->state = LCD_STATE_INITIALIZING;
  257. dev->reset_timeout = event_add_timeout_ms(LCD_RESET_TIMEOUT, lcd_reset_timeout_cb, 0, dev);
  258. }
  259. static int lcd_backlight(struct lcddev *dev, int enable)
  260. {
  261. if (dev->state != LCD_STATE_READY)
  262. return -1;
  263. #if (_LCD_DEBUG > 0)
  264. log_print(LOG_DEBUG, "%s(%d)", __FUNCTION__, enable);
  265. #endif /* (_LCD_DEBUG > 0) */
  266. if (dev->backlight_enabled != enable) {
  267. char cmd[] = A125_CMD_BACKLIGHT;
  268. cmd[2] = (enable) ? 0x01 : 0x00;
  269. lcd_write(dev, cmd, sizeof(cmd));
  270. dev->backlight_enabled = enable;
  271. }
  272. return 0;
  273. }
  274. static int lcd_backlight_timeout_cb(int timerid, void *privdata)
  275. {
  276. struct lcddev *dev = (struct lcddev *)privdata;
  277. dev->backlight_timeout = NULL;
  278. lcd_set_backlight(dev, 0);
  279. return -1; /* singleshot */
  280. }
  281. int lcd_set_backlight(struct lcddev *dev, int enable)
  282. {
  283. if (dev->backlight_timeout != NULL) {
  284. event_remove_timeout(dev->backlight_timeout);
  285. dev->backlight_timeout = NULL;
  286. }
  287. if (!enable) {
  288. lcd_pagecallback(dev, LCDPAGE_EVENT_BACKLIGHT);
  289. /* callback re-enabled backlight */
  290. if (dev->backlight_timeout != NULL) {
  291. return 0;
  292. }
  293. }
  294. int retval = lcd_backlight(dev, enable);
  295. if (enable) {
  296. /* start backlight timeout */
  297. dev->backlight_timeout = event_add_timeout_ms(dev->backlight_timeout_ms, lcd_backlight_timeout_cb, 0, dev);
  298. } else {
  299. /* disable updates (display is not visible) */
  300. if (dev->update_timeout != NULL) {
  301. event_remove_timeout(dev->update_timeout);
  302. dev->update_timeout = NULL;
  303. }
  304. /* disable scrolling (display is not visible) */
  305. if (dev->scroll_timeout != NULL) {
  306. event_remove_timeout(dev->scroll_timeout);
  307. dev->scroll_timeout = NULL;
  308. }
  309. }
  310. return retval;
  311. }
  312. static int lcd_update_timeout_cb(int timerid, void *privdata)
  313. {
  314. struct lcddev *dev = (struct lcddev *)privdata;
  315. dev->update_timeout = NULL;
  316. lcd_pagecallback(dev, LCDPAGE_EVENT_UPDATE);
  317. return -1; /* singleshot */
  318. }
  319. static void lcd_update_start(struct lcddev *dev, int update_ms)
  320. {
  321. dev->update_ms = update_ms;
  322. if (dev->update_timeout != NULL) {
  323. event_remove_timeout(dev->update_timeout);
  324. dev->update_timeout = NULL;
  325. }
  326. if (update_ms > 0) {
  327. dev->update_timeout = event_add_timeout_ms(update_ms, lcd_update_timeout_cb, 0, dev);
  328. }
  329. }
  330. static int lcd_read_cb(int fd, void *privdata)
  331. {
  332. struct lcddev *dev = (struct lcddev *)privdata;
  333. char buf[4];
  334. int size = (dev->state != LCD_STATE_INITIALIZING) ? sizeof(buf) : 2;
  335. int len = lcd_read(dev, buf, size);
  336. if (dev->state == LCD_STATE_INITIALIZING) {
  337. char expect[] = A125_EVENT_ACTINFO;
  338. if (len != sizeof(expect))
  339. return 0;
  340. if (memcmp(buf, expect, sizeof(expect)) == 0) {
  341. event_remove_timeout(dev->reset_timeout);
  342. dev->reset_timeout = NULL;
  343. dev->state = LCD_STATE_READY;
  344. /* trigger application to set data */
  345. lcd_pagecallback(dev, LCDPAGE_EVENT_ENTER);
  346. }
  347. } else if (dev->state == LCD_STATE_READY) {
  348. char expect1[] = A125_EVENT_BUTTON1;
  349. char expect2[] = A125_EVENT_BUTTON2;
  350. if (len != sizeof(expect1) && len != sizeof(expect2))
  351. return 0;
  352. if (memcmp(buf, expect1, sizeof(buf)) == 0) {
  353. lcd_pagecallback(dev, LCDPAGE_EVENT_BUTTON1);
  354. } else if (memcmp(buf, expect2, sizeof(buf)) == 0) {
  355. lcd_pagecallback(dev, LCDPAGE_EVENT_BUTTON2);
  356. }
  357. }
  358. return 0;
  359. }
  360. static int lcd_setline(struct lcddev *dev, int line, int len, const char *buf)
  361. {
  362. if (dev->state != LCD_STATE_READY)
  363. return -1;
  364. char cmd[20 +1] = A125_CMD_SETLINE;
  365. cmd[2] = line;
  366. memset(cmd +4, ' ', 16);
  367. memcpy(cmd +4, buf, (len > 16) ? 16 : len);
  368. cmd[20] = '\0';
  369. #if (_LCD_DEBUG > 0)
  370. log_print(LOG_DEBUG, "%s(%d, '%-16s')", __FUNCTION__, line, cmd +4);
  371. #endif /* (_LCD_DEBUG > 0) */
  372. lcd_write(dev, cmd, 20);
  373. return 0;
  374. }
  375. static int lcd_scroll_timeout_cb(int timerid, void *privdata)
  376. {
  377. struct lcddev *dev = (struct lcddev *)privdata;
  378. int i, reset_pos = 0;
  379. for (i = 0; i < 2; i++) {
  380. int line_pos = dev->scroll_pos;
  381. if (line_pos < 0)
  382. line_pos = 0;
  383. /* mark line as complete if one space is visible after message */
  384. if ((dev->line_length[i] - line_pos) <= 15) {
  385. reset_pos++;
  386. }
  387. /* do not scroll further if message is not visible */
  388. if ((dev->line_length[i] - line_pos) < 0) {
  389. line_pos = dev->line_length[i];
  390. }
  391. lcd_setline(dev, i, dev->line_length[i] - line_pos, dev->line_data[i] + line_pos);
  392. }
  393. dev->scroll_pos++;
  394. if (reset_pos == 2)
  395. dev->scroll_pos = -1;
  396. /* periodic */
  397. return 0;
  398. }
  399. static void lcd_scroll_start(struct lcddev *dev)
  400. {
  401. if (dev->scroll_timeout != NULL) {
  402. event_remove_timeout(dev->scroll_timeout);
  403. dev->scroll_timeout = NULL;
  404. }
  405. if ((dev->line_length[0] > 16) || (dev->line_length[1] > 16)) {
  406. dev->scroll_timeout = event_add_timeout_ms(LCD_SCROLL_SPEED, lcd_scroll_timeout_cb, 0, dev);
  407. }
  408. }
  409. int lcd_setlines(struct lcddev *dev, const char *line1, const char *line2)
  410. {
  411. int i;
  412. int line_length[2] = { 0, 0 };
  413. const char *line[2] = { line1, line2 };
  414. for (i = 0; i < 2; i++) {
  415. if (line[i] != NULL) {
  416. line_length[i] = strlen(line[i]);
  417. if ((line_length[i] == dev->line_length[i]) && (dev->line_data[i] != NULL)) {
  418. if (memcmp(line[i], dev->line_data[i], line_length[i]) == 0) {
  419. /* same data, no update needed */
  420. continue;
  421. }
  422. }
  423. }
  424. if (dev->line_data[i] != NULL) {
  425. free((void *)dev->line_data[i]);
  426. dev->line_data[i] = NULL;
  427. }
  428. if (line[i] != NULL) {
  429. dev->line_data[i] = strdup(line[i]);
  430. }
  431. dev->line_length[i] = line_length[i];
  432. if (dev->backlight_enabled) {
  433. lcd_setline(dev, i, dev->line_length[i], dev->line_data[i]);
  434. dev->scroll_pos = 0;
  435. lcd_scroll_start(dev);
  436. }
  437. }
  438. return 0;
  439. }
  440. static void lcd_pagecallback(struct lcddev *dev, int event)
  441. {
  442. if (dev->state != LCD_STATE_READY) {
  443. return;
  444. }
  445. if (dev->current_page == NULL) {
  446. if (list_empty(&dev->page_list)) {
  447. return;
  448. }
  449. dev->current_page = list_entry(dev->page_list.next, struct lcdpage, list);
  450. }
  451. if ((dev->backlight_enabled == 0x00) && ((event == LCDPAGE_EVENT_BUTTON1) || (event == LCDPAGE_EVENT_BUTTON2))) {
  452. lcd_set_backlight(dev, 1);
  453. event = LCDPAGE_EVENT_UPDATE;
  454. /* restart scrolling if needed */
  455. lcd_scroll_start(dev);
  456. }
  457. int next;
  458. do {
  459. #if (_LCD_DEBUG > 0)
  460. log_print(LOG_DEBUG, "%s: cb(%p, 0x%x)", __FUNCTION__, dev->current_page, event);
  461. #endif
  462. int retval = dev->current_page->callback(dev, event, dev->current_page->privdata);
  463. #if (_LCD_DEBUG > 0)
  464. log_print(LOG_DEBUG, "%s: cb(%p, 0x%x) => 0x%x", __FUNCTION__, dev->current_page, event, retval);
  465. #endif
  466. next = (retval == LCDPAGE_COMMAND_NEXT);
  467. if (next) {
  468. struct lcdpage *page = dev->current_page;
  469. if (page->list.next != &dev->page_list) {
  470. page = list_entry(page->list.next, struct lcdpage, list);
  471. } else {
  472. page = list_entry(page->list.next->next, struct lcdpage, list);
  473. }
  474. /* no other page ready */
  475. if (page == dev->current_page) {
  476. break;
  477. }
  478. /* remove update interval for next page */
  479. if (dev->update_timeout != NULL) {
  480. event_remove_timeout(dev->update_timeout);
  481. dev->update_timeout = NULL;
  482. }
  483. /* remove scroll interval for next page */
  484. if (dev->scroll_timeout != NULL) {
  485. event_remove_timeout(dev->scroll_timeout);
  486. dev->scroll_timeout = NULL;
  487. }
  488. dev->current_page->callback(dev, LCDPAGE_EVENT_EXIT, dev->current_page->privdata);
  489. dev->current_page = page;
  490. event = LCDPAGE_EVENT_ENTER;
  491. /* retval is update interval */
  492. } else if ((event != LCDPAGE_EVENT_BACKLIGHT) && (event != LCDPAGE_EVENT_EXIT)) {
  493. lcd_update_start(dev, retval);
  494. }
  495. } while (next);
  496. }
  497. int lcd_addpage_cb(struct lcddev *dev,
  498. int priority,
  499. int (*event_callback)(struct lcddev *dev, int event, void *privdata),
  500. void *event_privdata)
  501. {
  502. struct lcdpage *page = malloc(sizeof(struct lcdpage));
  503. if (page == NULL) {
  504. log_print(LOG_ERROR, "%s(): out of memory", __FUNCTION__);
  505. return -1;
  506. }
  507. page->priority = priority;
  508. page->callback = event_callback;
  509. page->privdata = event_privdata;
  510. int inserted = 0;
  511. struct lcdpage *search;
  512. list_for_each_entry(search, &dev->page_list, list) {
  513. if (page->priority > search->priority) {
  514. list_add_tail(&page->list, &search->list);
  515. inserted = 1;
  516. break;
  517. }
  518. }
  519. if (!inserted) {
  520. list_add_tail(&page->list, &dev->page_list);
  521. }
  522. if (dev->current_page == NULL) {
  523. lcd_pagecallback(dev, LCDPAGE_EVENT_ENTER);
  524. }
  525. return 0;
  526. }
  527. struct lcddev * lcd_open(const char *devicename, int backlight_timeout)
  528. {
  529. struct lcddev *dev = malloc(sizeof(struct lcddev));
  530. if (dev == NULL) {
  531. log_print(LOG_ERROR, "%s(): out of memory", __FUNCTION__);
  532. return NULL;
  533. }
  534. memset(dev, 0, sizeof(struct lcddev));
  535. INIT_LIST_HEAD(&dev->page_list);
  536. int retval;
  537. #if (_LCD_DUMMY)
  538. if (strncmp(devicename, "dummy", 5) == 0) {
  539. retval = lcd_fakedevice_open(dev);
  540. } else
  541. #endif /* (_LCD_DUMMY) */
  542. {
  543. retval = lcd_realdevice_open(dev, devicename);
  544. }
  545. if (retval < 0) {
  546. free(dev);
  547. return NULL;
  548. }
  549. dev->read_event = event_add_readfd(NULL, dev->fd, lcd_read_cb, dev);
  550. dev->backlight_timeout_ms = backlight_timeout * 1000;
  551. lcd_reset(dev);
  552. return dev;
  553. }