add the correct input file to makeindex call
[Rockbox.git] / firmware / drivers / lcd-h100-remote.c
blob0592f60e003eef4c392c2ac3ac2eb76c3b95fb52
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2005 by Richard S. La Charité III
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include "config.h"
21 #include "cpu.h"
22 #include "lcd.h"
23 #include "lcd-remote.h"
24 #include "kernel.h"
25 #include "thread.h"
26 #include <string.h>
27 #include <stdlib.h>
28 #include "file.h"
29 #include "debug.h"
30 #include "system.h"
31 #include "font.h"
32 #include "rbunicode.h"
33 #include "bidi.h"
35 /*** definitions ***/
37 #define LCD_REMOTE_CNTL_ADC_NORMAL 0xa0
38 #define LCD_REMOTE_CNTL_ADC_REVERSE 0xa1
39 #define LCD_REMOTE_CNTL_SHL_NORMAL 0xc0
40 #define LCD_REMOTE_CNTL_SHL_REVERSE 0xc8
41 #define LCD_REMOTE_CNTL_DISPLAY_ON_OFF 0xae
42 #define LCD_REMOTE_CNTL_ENTIRE_ON_OFF 0xa4
43 #define LCD_REMOTE_CNTL_REVERSE_ON_OFF 0xa6
44 #define LCD_REMOTE_CNTL_NOP 0xe3
45 #define LCD_REMOTE_CNTL_POWER_CONTROL 0x2b
46 #define LCD_REMOTE_CNTL_SELECT_REGULATOR 0x20
47 #define LCD_REMOTE_CNTL_SELECT_BIAS 0xa2
48 #define LCD_REMOTE_CNTL_SELECT_VOLTAGE 0x81
49 #define LCD_REMOTE_CNTL_INIT_LINE 0x40
50 #define LCD_REMOTE_CNTL_SET_PAGE_ADDRESS 0xB0
52 #define LCD_REMOTE_CNTL_HIGHCOL 0x10 /* Upper column address */
53 #define LCD_REMOTE_CNTL_LOWCOL 0x00 /* Lower column address */
55 #define CS_LO and_l(~0x00000004, &GPIO1_OUT)
56 #define CS_HI or_l(0x00000004, &GPIO1_OUT)
57 #define CLK_LO and_l(~0x10000000, &GPIO_OUT)
58 #define CLK_HI or_l(0x10000000, &GPIO_OUT)
59 #define DATA_LO and_l(~0x00040000, &GPIO1_OUT)
60 #define DATA_HI or_l(0x00040000, &GPIO1_OUT)
61 #define RS_LO and_l(~0x00010000, &GPIO_OUT)
62 #define RS_HI or_l(0x00010000, &GPIO_OUT)
64 #define SCROLLABLE_LINES 13
66 /*** globals ***/
68 fb_remote_data lcd_remote_framebuffer[LCD_REMOTE_HEIGHT/8][LCD_REMOTE_WIDTH]
69 IBSS_ATTR;
71 static int drawmode = DRMODE_SOLID;
72 static int xmargin = 0;
73 static int ymargin = 0;
74 static int curfont = FONT_SYSFIXED;
76 #ifndef SIMULATOR
77 static int xoffset; /* needed for flip */
79 /* timeout counter for deasserting /CS after access, <0 means not counting */
80 static int cs_countdown IDATA_ATTR = 0;
81 #define CS_TIMEOUT (HZ/10)
83 #ifdef HAVE_REMOTE_LCD_TICKING
84 /* If set to true, will prevent "ticking" to headphones. */
85 static bool emireduce = false;
86 static int byte_delay = 0;
87 #endif
89 /* remote hotplug */
90 static struct event_queue remote_scroll_queue;
91 #define REMOTE_INIT_LCD 1
92 #define REMOTE_DEINIT_LCD 2
94 static bool remote_initialized = false;
95 static int _remote_type = REMOTETYPE_UNPLUGGED;
97 /* cached settings values */
98 static bool cached_invert = false;
99 static bool cached_flip = false;
100 static int cached_contrast = DEFAULT_REMOTE_CONTRAST_SETTING;
101 #endif
103 /* scrolling */
104 static volatile int scrolling_lines=0; /* Bitpattern of which lines are scrolling */
105 static void scroll_thread(void);
106 static long scroll_stack[DEFAULT_STACK_SIZE/sizeof(long)];
107 static const char scroll_name[] = "remote_scroll";
108 static int scroll_ticks = 12; /* # of ticks between updates*/
109 static int scroll_delay = HZ/2; /* ticks delay before start */
110 static int scroll_step = 6; /* pixels per scroll step */
111 static int bidir_limit = 50; /* percent */
112 static struct scrollinfo scroll[SCROLLABLE_LINES];
114 static const char scroll_tick_table[16] = {
115 /* Hz values:
116 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */
117 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3
120 /*** driver routines ***/
122 #ifndef SIMULATOR
124 #ifdef HAVE_REMOTE_LCD_TICKING
125 static inline void _byte_delay(int delay)
127 asm (
128 "move.l %[dly], %%d0 \n"
129 "ble.s 2f \n"
130 "1: \n"
131 "subq.l #1, %%d0 \n"
132 "bne.s 1b \n"
133 "2: \n"
134 : /* outputs */
135 : /* inputs */
136 [dly]"d"(delay)
137 : /* clobbers */
138 "d0"
141 #endif /* HAVE_REMOTE_LCD_TICKING */
143 /* Standard low-level byte writer. Requires CLK low on entry */
144 static inline void _write_byte(unsigned data)
146 asm volatile (
147 "move.l (%[gpo1]), %%d0 \n" /* Get current state of data line */
148 "and.l %[dbit], %%d0 \n"
149 "beq.s 1f \n" /* and set it as previous-state bit */
150 "bset #8, %[data] \n"
151 "1: \n"
152 "move.l %[data], %%d0 \n" /* Compute the 'bit derivative', i.e. a value */
153 "lsr.l #1, %%d0 \n" /* with 1's where the data changes from the */
154 "eor.l %%d0, %[data] \n" /* previous state, and 0's where it doesn't */
155 "swap %[data] \n" /* Shift data to upper byte */
156 "lsl.l #8, %[data] \n"
158 "lsl.l #1,%[data] \n" /* Shift out MSB */
159 "bcc.s 1f \n"
160 "eor.l %[dbit], (%[gpo1]) \n" /* 1: flip DATA */
161 "1: \n"
162 "eor.l %[cbit], (%[gpo0]) \n" /* Flip CLK */
163 "eor.l %[cbit], (%[gpo0]) \n" /* Flip CLK */
165 "lsl.l #1,%[data] \n" /* ..unrolled.. */
166 "bcc.s 1f \n"
167 "eor.l %[dbit], (%[gpo1]) \n"
168 "1: \n"
169 "eor.l %[cbit], (%[gpo0]) \n"
170 "eor.l %[cbit], (%[gpo0]) \n"
172 "lsl.l #1,%[data] \n"
173 "bcc.s 1f \n"
174 "eor.l %[dbit], (%[gpo1]) \n"
175 "1: \n"
176 "eor.l %[cbit], (%[gpo0]) \n"
177 "eor.l %[cbit], (%[gpo0]) \n"
179 "lsl.l #1,%[data] \n"
180 "bcc.s 1f \n"
181 "eor.l %[dbit], (%[gpo1]) \n"
182 "1: \n"
183 "eor.l %[cbit], (%[gpo0]) \n"
184 "eor.l %[cbit], (%[gpo0]) \n"
186 "lsl.l #1,%[data] \n"
187 "bcc.s 1f \n"
188 "eor.l %[dbit], (%[gpo1]) \n"
189 "1: \n"
190 "eor.l %[cbit], (%[gpo0]) \n"
191 "eor.l %[cbit], (%[gpo0]) \n"
193 "lsl.l #1,%[data] \n"
194 "bcc.s 1f \n"
195 "eor.l %[dbit], (%[gpo1]) \n"
196 "1: \n"
197 "eor.l %[cbit], (%[gpo0]) \n"
198 "eor.l %[cbit], (%[gpo0]) \n"
200 "lsl.l #1,%[data] \n"
201 "bcc.s 1f \n"
202 "eor.l %[dbit], (%[gpo1]) \n"
203 "1: \n"
204 "eor.l %[cbit], (%[gpo0]) \n"
205 "eor.l %[cbit], (%[gpo0]) \n"
207 "lsl.l #1,%[data] \n"
208 "bcc.s 1f \n"
209 "eor.l %[dbit], (%[gpo1]) \n"
210 "1: \n"
211 "eor.l %[cbit], (%[gpo0]) \n"
212 "eor.l %[cbit], (%[gpo0]) \n"
213 : /* outputs */
214 [data]"+d"(data)
215 : /* inputs */
216 [gpo0]"a"(&GPIO_OUT),
217 [cbit]"d"(0x10000000),
218 [gpo1]"a"(&GPIO1_OUT),
219 [dbit]"d"(0x00040000)
220 : /* clobbers */
221 "d0"
225 /* Fast low-level byte writer. Don't use with high CPU clock.
226 * Requires CLK low on entry */
227 static inline void _write_fast(unsigned data)
229 asm volatile (
230 "move.w %%sr,%%d3 \n" /* Get current interrupt level */
231 "move.w #0x2700,%%sr \n" /* Disable interrupts */
233 "move.l (%[gpo1]), %%d0 \n" /* Get current state of data port */
234 "move.l %%d0, %%d1 \n"
235 "and.l %[dbit], %%d1 \n" /* Check current state of data line */
236 "beq.s 1f \n" /* and set it as previous-state bit */
237 "bset #8, %[data] \n"
238 "1: \n"
239 "move.l %[data], %%d1 \n" /* Compute the 'bit derivative', i.e. a value */
240 "lsr.l #1, %%d1 \n" /* with 1's where the data changes from the */
241 "eor.l %%d1, %[data] \n" /* previous state, and 0's where it doesn't */
242 "swap %[data] \n" /* Shift data to upper byte */
243 "lsl.l #8, %[data] \n"
245 "move.l (%[gpo0]), %%d1 \n" /* Get current state of clock port */
246 "move.l %[cbit], %%d2 \n" /* Precalculate opposite state of clock line */
247 "eor.l %%d1, %%d2 \n"
249 "lsl.l #1,%[data] \n" /* Shift out MSB */
250 "bcc.s 1f \n"
251 "eor.l %[dbit], %%d0 \n" /* 1: flip data bit */
252 "move.l %%d0, (%[gpo1]) \n" /* and output new DATA state */
253 "1: \n"
254 "move.l %%d2, (%[gpo0]) \n" /* Set CLK */
255 "move.l %%d1, (%[gpo0]) \n" /* Reset CLK */
257 "lsl.l #1,%[data] \n" /* ..unrolled.. */
258 "bcc.s 1f \n"
259 "eor.l %[dbit], %%d0 \n"
260 "move.l %%d0, (%[gpo1]) \n"
261 "1: \n"
262 "move.l %%d2, (%[gpo0]) \n"
263 "move.l %%d1, (%[gpo0]) \n"
265 "lsl.l #1,%[data] \n"
266 "bcc.s 1f \n"
267 "eor.l %[dbit], %%d0 \n"
268 "move.l %%d0, (%[gpo1]) \n"
269 "1: \n"
270 "move.l %%d2, (%[gpo0]) \n"
271 "move.l %%d1, (%[gpo0]) \n"
273 "lsl.l #1,%[data] \n"
274 "bcc.s 1f \n"
275 "eor.l %[dbit], %%d0 \n"
276 "move.l %%d0, (%[gpo1]) \n"
277 "1: \n"
278 "move.l %%d2, (%[gpo0]) \n"
279 "move.l %%d1, (%[gpo0]) \n"
281 "lsl.l #1,%[data] \n"
282 "bcc.s 1f \n"
283 "eor.l %[dbit], %%d0 \n"
284 "move.l %%d0, (%[gpo1]) \n"
285 "1: \n"
286 "move.l %%d2, (%[gpo0]) \n"
287 "move.l %%d1, (%[gpo0]) \n"
289 "lsl.l #1,%[data] \n"
290 "bcc.s 1f \n"
291 "eor.l %[dbit], %%d0 \n"
292 "move.l %%d0, (%[gpo1]) \n"
293 "1: \n"
294 "move.l %%d2, (%[gpo0]) \n"
295 "move.l %%d1, (%[gpo0]) \n"
297 "lsl.l #1,%[data] \n"
298 "bcc.s 1f \n"
299 "eor.l %[dbit], %%d0 \n"
300 "move.l %%d0, (%[gpo1]) \n"
301 "1: \n"
302 "move.l %%d2, (%[gpo0]) \n"
303 "move.l %%d1, (%[gpo0]) \n"
305 "lsl.l #1,%[data] \n"
306 "bcc.s 1f \n"
307 "eor.l %[dbit], %%d0 \n"
308 "move.l %%d0, (%[gpo1]) \n"
309 "1: \n"
310 "move.l %%d2, (%[gpo0]) \n"
311 "move.l %%d1, (%[gpo0]) \n"
313 "move.w %%d3, %%sr \n" /* Restore interrupt level */
314 : /* outputs */
315 [data]"+d"(data)
316 : /* inputs */
317 [gpo0]"a"(&GPIO_OUT),
318 [cbit]"i"(0x10000000),
319 [gpo1]"a"(&GPIO1_OUT),
320 [dbit]"d"(0x00040000)
321 : /* clobbers */
322 "d0", "d1", "d2", "d3"
326 void lcd_remote_write_command(int cmd)
328 cs_countdown = 0;
329 RS_LO;
330 CS_LO;
332 _write_byte(cmd);
333 #ifdef HAVE_REMOTE_LCD_TICKING
334 _byte_delay(byte_delay - 148);
335 #endif
337 cs_countdown = CS_TIMEOUT;
340 void lcd_remote_write_command_ex(int cmd, int data)
342 cs_countdown = 0;
343 RS_LO;
344 CS_LO;
346 _write_byte(cmd);
347 #ifdef HAVE_REMOTE_LCD_TICKING
348 _byte_delay(byte_delay - 148);
349 #endif
350 _write_byte(data);
351 #ifdef HAVE_REMOTE_LCD_TICKING
352 _byte_delay(byte_delay - 148);
353 #endif
355 cs_countdown = CS_TIMEOUT;
358 void lcd_remote_write_data(const unsigned char* p_bytes, int count) ICODE_ATTR;
359 void lcd_remote_write_data(const unsigned char* p_bytes, int count)
361 const unsigned char *p_end = p_bytes + count;
363 cs_countdown = 0;
364 RS_HI;
365 CS_LO;
367 /* This is safe as long as lcd_remote_write_data() isn't called from within
368 * an ISR. */
369 if (cpu_frequency < 50000000)
371 while (p_bytes < p_end)
373 _write_fast(*p_bytes++);
374 #ifdef HAVE_REMOTE_LCD_TICKING
375 _byte_delay(byte_delay - 87);
376 #endif
379 else
381 while (p_bytes < p_end)
383 _write_byte(*p_bytes++);
384 #ifdef HAVE_REMOTE_LCD_TICKING
385 _byte_delay(byte_delay - 148);
386 #endif
390 cs_countdown = CS_TIMEOUT;
392 #endif /* !SIMULATOR */
394 /*** hardware configuration ***/
396 int lcd_remote_default_contrast(void)
398 return DEFAULT_REMOTE_CONTRAST_SETTING;
401 #ifndef SIMULATOR
403 #ifdef HAVE_REMOTE_LCD_TICKING
404 void lcd_remote_emireduce(bool state)
406 emireduce = state;
408 #endif
410 void lcd_remote_powersave(bool on)
412 if (remote_initialized)
414 lcd_remote_write_command(LCD_REMOTE_CNTL_DISPLAY_ON_OFF | (on ? 0 : 1));
415 lcd_remote_write_command(LCD_REMOTE_CNTL_ENTIRE_ON_OFF | (on ? 1 : 0));
419 void lcd_remote_set_contrast(int val)
421 cached_contrast = val;
422 if (remote_initialized)
423 lcd_remote_write_command_ex(LCD_REMOTE_CNTL_SELECT_VOLTAGE, val);
426 void lcd_remote_set_invert_display(bool yesno)
428 cached_invert = yesno;
429 if (remote_initialized)
430 lcd_remote_write_command(LCD_REMOTE_CNTL_REVERSE_ON_OFF | (yesno?1:0));
433 /* turn the display upside down (call lcd_remote_update() afterwards) */
434 void lcd_remote_set_flip(bool yesno)
436 cached_flip = yesno;
437 if (yesno)
439 xoffset = 0;
440 if (remote_initialized)
442 lcd_remote_write_command(LCD_REMOTE_CNTL_ADC_NORMAL);
443 lcd_remote_write_command(LCD_REMOTE_CNTL_SHL_NORMAL);
446 else
448 xoffset = 132 - LCD_REMOTE_WIDTH;
449 if (remote_initialized)
451 lcd_remote_write_command(LCD_REMOTE_CNTL_ADC_REVERSE);
452 lcd_remote_write_command(LCD_REMOTE_CNTL_SHL_REVERSE);
457 /* The actual LCD init */
458 static void remote_lcd_init(void)
460 CS_HI;
461 CLK_LO;
462 lcd_remote_write_command(LCD_REMOTE_CNTL_SELECT_BIAS | 0x0);
464 lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x5);
465 sleep(1);
466 lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x6);
467 sleep(1);
468 lcd_remote_write_command(LCD_REMOTE_CNTL_POWER_CONTROL | 0x7);
470 lcd_remote_write_command(LCD_REMOTE_CNTL_SELECT_REGULATOR | 0x4); // 0x4 Select regulator @ 5.0 (default);
472 sleep(1);
474 lcd_remote_write_command(LCD_REMOTE_CNTL_INIT_LINE | 0x0); // init line
475 lcd_remote_write_command(LCD_REMOTE_CNTL_SET_PAGE_ADDRESS | 0x0); // page address
476 lcd_remote_write_command_ex(0x10, 0x00); // Column MSB + LSB
478 lcd_remote_write_command(LCD_REMOTE_CNTL_DISPLAY_ON_OFF | 1);
480 remote_initialized = true;
482 lcd_remote_set_flip(cached_flip);
483 lcd_remote_set_contrast(cached_contrast);
484 lcd_remote_set_invert_display(cached_invert);
487 int remote_type(void)
489 return _remote_type;
492 /* Monitor remote hotswap */
493 static void remote_tick(void)
495 static bool last_status = false;
496 static int countdown = 0;
497 static int init_delay = 0;
498 bool current_status;
499 int val;
500 int level;
502 current_status = ((GPIO_READ & 0x40000000) == 0);
503 /* Only report when the status has changed */
504 if (current_status != last_status)
506 last_status = current_status;
507 countdown = current_status ? 20*HZ : 1;
509 else
511 /* Count down until it gets negative */
512 if (countdown >= 0)
513 countdown--;
515 if (current_status)
517 if (!(countdown % 8))
519 /* Determine which type of remote it is */
520 level = set_irq_level(HIGHEST_IRQ_LEVEL);
521 val = adc_scan(ADC_REMOTEDETECT);
522 set_irq_level(level);
524 if (val < ADCVAL_H100_LCD_REMOTE_HOLD)
526 if (val < ADCVAL_H100_LCD_REMOTE)
527 if (val < ADCVAL_H300_LCD_REMOTE)
528 _remote_type = REMOTETYPE_H300_LCD; /* hold off */
529 else
530 _remote_type = REMOTETYPE_H100_LCD; /* hold off */
531 else
532 if (val < ADCVAL_H300_LCD_REMOTE_HOLD)
533 _remote_type = REMOTETYPE_H300_LCD; /* hold on */
534 else
535 _remote_type = REMOTETYPE_H100_LCD; /* hold on */
537 if (--init_delay <= 0)
539 queue_post(&remote_scroll_queue, REMOTE_INIT_LCD, 0);
540 init_delay = 6;
543 else
545 _remote_type = REMOTETYPE_H300_NONLCD; /* hold on or off */
549 else
551 if (countdown == 0)
553 _remote_type = REMOTETYPE_UNPLUGGED;
555 queue_post(&remote_scroll_queue, REMOTE_DEINIT_LCD, 0);
560 /* handle chip select timeout */
561 if (cs_countdown >= 0)
562 cs_countdown--;
563 if (cs_countdown == 0)
564 CS_HI;
566 #endif /* !SIMULATOR */
568 /* LCD init */
569 #ifdef SIMULATOR
571 void lcd_remote_init(void)
573 create_thread(scroll_thread, scroll_stack,
574 sizeof(scroll_stack), scroll_name IF_PRIO(, PRIORITY_USER_INTERFACE));
576 #else /* !SIMULATOR */
578 /* Initialise ports and kick off monitor */
579 void lcd_remote_init(void)
581 #ifdef IRIVER_H300_SERIES
582 or_l(0x10010000, &GPIO_FUNCTION); /* GPIO16: RS
583 GPIO28: CLK */
585 or_l(0x00040006, &GPIO1_FUNCTION); /* GPO33: Backlight
586 GPIO34: CS
587 GPIO50: Data */
588 or_l(0x10010000, &GPIO_ENABLE);
589 or_l(0x00040006, &GPIO1_ENABLE);
590 #else
591 or_l(0x10010800, &GPIO_FUNCTION); /* GPIO11: Backlight
592 GPIO16: RS
593 GPIO28: CLK */
595 or_l(0x00040004, &GPIO1_FUNCTION); /* GPIO34: CS
596 GPIO50: Data */
597 or_l(0x10010800, &GPIO_ENABLE);
598 or_l(0x00040004, &GPIO1_ENABLE);
599 #endif
600 lcd_remote_clear_display();
602 /* private queue */
603 queue_init(&remote_scroll_queue, false);
604 tick_add_task(remote_tick);
605 create_thread(scroll_thread, scroll_stack,
606 sizeof(scroll_stack), scroll_name IF_PRIO(, PRIORITY_USER_INTERFACE));
609 /*** update functions ***/
611 /* Update the display.
612 This must be called after all other LCD functions that change the display. */
613 void lcd_remote_update(void) ICODE_ATTR;
614 void lcd_remote_update(void)
616 int y;
618 if (!remote_initialized)
619 return;
621 #ifdef HAVE_REMOTE_LCD_TICKING
622 /* Adjust byte delay for emi reduction. */
623 byte_delay = emireduce ? cpu_frequency / 197600 + 28: 0;
624 #endif
626 /* Copy display bitmap to hardware */
627 for (y = 0; y < LCD_REMOTE_HEIGHT/8; y++)
629 lcd_remote_write_command(LCD_REMOTE_CNTL_SET_PAGE_ADDRESS | y);
630 lcd_remote_write_command(LCD_REMOTE_CNTL_HIGHCOL | ((xoffset >> 4) & 0xf));
631 lcd_remote_write_command(LCD_REMOTE_CNTL_LOWCOL | (xoffset & 0xf));
632 lcd_remote_write_data(lcd_remote_framebuffer[y], LCD_REMOTE_WIDTH);
636 /* Update a fraction of the display. */
637 void lcd_remote_update_rect(int, int, int, int) ICODE_ATTR;
638 void lcd_remote_update_rect(int x, int y, int width, int height)
640 int ymax;
642 if (!remote_initialized)
643 return;
645 /* The Y coordinates have to work on even 8 pixel rows */
646 ymax = (y + height-1) >> 3;
647 y >>= 3;
649 if(x + width > LCD_REMOTE_WIDTH)
650 width = LCD_REMOTE_WIDTH - x;
651 if (width <= 0)
652 return; /* nothing left to do, 0 is harmful to lcd_write_data() */
653 if(ymax >= LCD_REMOTE_HEIGHT/8)
654 ymax = LCD_REMOTE_HEIGHT/8-1;
656 #ifdef HAVE_REMOTE_LCD_TICKING
657 /* Adjust byte delay for emi reduction */
658 byte_delay = emireduce ? cpu_frequency / 197600 + 28: 0;
659 #endif
661 /* Copy specified rectange bitmap to hardware */
662 for (; y <= ymax; y++)
664 lcd_remote_write_command(LCD_REMOTE_CNTL_SET_PAGE_ADDRESS | y);
665 lcd_remote_write_command(LCD_REMOTE_CNTL_HIGHCOL | (((x+xoffset) >> 4) & 0xf));
666 lcd_remote_write_command(LCD_REMOTE_CNTL_LOWCOL | ((x+xoffset) & 0xf));
667 lcd_remote_write_data(&lcd_remote_framebuffer[y][x], width);
670 #endif /* !SIMULATOR */
672 /*** parameter handling ***/
674 void lcd_remote_set_drawmode(int mode)
676 drawmode = mode & (DRMODE_SOLID|DRMODE_INVERSEVID);
679 int lcd_remote_get_drawmode(void)
681 return drawmode;
684 void lcd_remote_setmargins(int x, int y)
686 xmargin = x;
687 ymargin = y;
690 int lcd_remote_getxmargin(void)
692 return xmargin;
695 int lcd_remote_getymargin(void)
697 return ymargin;
701 void lcd_remote_setfont(int newfont)
703 curfont = newfont;
706 int lcd_remote_getstringsize(const unsigned char *str, int *w, int *h)
708 return font_getstringsize(str, w, h, curfont);
711 /*** low-level drawing functions ***/
713 static void setpixel(int x, int y)
715 lcd_remote_framebuffer[y>>3][x] |= 1 << (y & 7);
718 static void clearpixel(int x, int y)
720 lcd_remote_framebuffer[y>>3][x] &= ~(1 << (y & 7));
723 static void flippixel(int x, int y)
725 lcd_remote_framebuffer[y>>3][x] ^= 1 << (y & 7);
728 static void nopixel(int x, int y)
730 (void)x;
731 (void)y;
734 lcd_remote_pixelfunc_type* const lcd_remote_pixelfuncs[8] = {
735 flippixel, nopixel, setpixel, setpixel,
736 nopixel, clearpixel, nopixel, clearpixel
739 static void flipblock(fb_remote_data *address, unsigned mask, unsigned bits)
740 ICODE_ATTR;
741 static void flipblock(fb_remote_data *address, unsigned mask, unsigned bits)
743 *address ^= bits & mask;
746 static void bgblock(fb_remote_data *address, unsigned mask, unsigned bits)
747 ICODE_ATTR;
748 static void bgblock(fb_remote_data *address, unsigned mask, unsigned bits)
750 *address &= bits | ~mask;
753 static void fgblock(fb_remote_data *address, unsigned mask, unsigned bits)
754 ICODE_ATTR;
755 static void fgblock(fb_remote_data *address, unsigned mask, unsigned bits)
757 *address |= bits & mask;
760 static void solidblock(fb_remote_data *address, unsigned mask, unsigned bits)
761 ICODE_ATTR;
762 static void solidblock(fb_remote_data *address, unsigned mask, unsigned bits)
764 unsigned data = *address;
766 bits ^= data;
767 *address = data ^ (bits & mask);
770 static void flipinvblock(fb_remote_data *address, unsigned mask, unsigned bits)
771 ICODE_ATTR;
772 static void flipinvblock(fb_remote_data *address, unsigned mask, unsigned bits)
774 *address ^= ~bits & mask;
777 static void bginvblock(fb_remote_data *address, unsigned mask, unsigned bits)
778 ICODE_ATTR;
779 static void bginvblock(fb_remote_data *address, unsigned mask, unsigned bits)
781 *address &= ~(bits & mask);
784 static void fginvblock(fb_remote_data *address, unsigned mask, unsigned bits)
785 ICODE_ATTR;
786 static void fginvblock(fb_remote_data *address, unsigned mask, unsigned bits)
788 *address |= ~bits & mask;
791 static void solidinvblock(fb_remote_data *address, unsigned mask, unsigned bits)
792 ICODE_ATTR;
793 static void solidinvblock(fb_remote_data *address, unsigned mask, unsigned bits)
795 unsigned data = *address;
797 bits = ~bits ^ data;
798 *address = data ^ (bits & mask);
801 lcd_remote_blockfunc_type* const lcd_remote_blockfuncs[8] = {
802 flipblock, bgblock, fgblock, solidblock,
803 flipinvblock, bginvblock, fginvblock, solidinvblock
806 /*** drawing functions ***/
808 /* Clear the whole display */
809 void lcd_remote_clear_display(void)
811 unsigned bits = (drawmode & DRMODE_INVERSEVID) ? 0xFFu : 0;
813 memset(lcd_remote_framebuffer, bits, sizeof lcd_remote_framebuffer);
814 scrolling_lines = 0;
817 /* Set a single pixel */
818 void lcd_remote_drawpixel(int x, int y)
820 if (((unsigned)x < LCD_REMOTE_WIDTH) && ((unsigned)y < LCD_REMOTE_HEIGHT))
821 lcd_remote_pixelfuncs[drawmode](x, y);
824 /* Draw a line */
825 void lcd_remote_drawline(int x1, int y1, int x2, int y2)
827 int numpixels;
828 int i;
829 int deltax, deltay;
830 int d, dinc1, dinc2;
831 int x, xinc1, xinc2;
832 int y, yinc1, yinc2;
833 lcd_remote_pixelfunc_type *pfunc = lcd_remote_pixelfuncs[drawmode];
835 deltax = abs(x2 - x1);
836 deltay = abs(y2 - y1);
837 xinc2 = 1;
838 yinc2 = 1;
840 if (deltax >= deltay)
842 numpixels = deltax;
843 d = 2 * deltay - deltax;
844 dinc1 = deltay * 2;
845 dinc2 = (deltay - deltax) * 2;
846 xinc1 = 1;
847 yinc1 = 0;
849 else
851 numpixels = deltay;
852 d = 2 * deltax - deltay;
853 dinc1 = deltax * 2;
854 dinc2 = (deltax - deltay) * 2;
855 xinc1 = 0;
856 yinc1 = 1;
858 numpixels++; /* include endpoints */
860 if (x1 > x2)
862 xinc1 = -xinc1;
863 xinc2 = -xinc2;
866 if (y1 > y2)
868 yinc1 = -yinc1;
869 yinc2 = -yinc2;
872 x = x1;
873 y = y1;
875 for (i = 0; i < numpixels; i++)
877 if (((unsigned)x < LCD_REMOTE_WIDTH) && ((unsigned)y < LCD_REMOTE_HEIGHT))
878 pfunc(x, y);
880 if (d < 0)
882 d += dinc1;
883 x += xinc1;
884 y += yinc1;
886 else
888 d += dinc2;
889 x += xinc2;
890 y += yinc2;
895 /* Draw a horizontal line (optimised) */
896 void lcd_remote_hline(int x1, int x2, int y)
898 int x;
899 fb_remote_data *dst, *dst_end;
900 unsigned mask;
901 lcd_remote_blockfunc_type *bfunc;
903 /* direction flip */
904 if (x2 < x1)
906 x = x1;
907 x1 = x2;
908 x2 = x;
911 /* nothing to draw? */
912 if (((unsigned)y >= LCD_REMOTE_HEIGHT) || (x1 >= LCD_REMOTE_WIDTH)
913 || (x2 < 0))
914 return;
916 /* clipping */
917 if (x1 < 0)
918 x1 = 0;
919 if (x2 >= LCD_REMOTE_WIDTH)
920 x2 = LCD_REMOTE_WIDTH-1;
922 bfunc = lcd_remote_blockfuncs[drawmode];
923 dst = &lcd_remote_framebuffer[y>>3][x1];
924 mask = 1 << (y & 7);
926 dst_end = dst + x2 - x1;
928 bfunc(dst++, mask, 0xFFu);
929 while (dst <= dst_end);
932 /* Draw a vertical line (optimised) */
933 void lcd_remote_vline(int x, int y1, int y2)
935 int ny;
936 fb_remote_data *dst;
937 unsigned mask, mask_bottom;
938 lcd_remote_blockfunc_type *bfunc;
940 /* direction flip */
941 if (y2 < y1)
943 ny = y1;
944 y1 = y2;
945 y2 = ny;
948 /* nothing to draw? */
949 if (((unsigned)x >= LCD_REMOTE_WIDTH) || (y1 >= LCD_REMOTE_HEIGHT)
950 || (y2 < 0))
951 return;
953 /* clipping */
954 if (y1 < 0)
955 y1 = 0;
956 if (y2 >= LCD_REMOTE_HEIGHT)
957 y2 = LCD_REMOTE_HEIGHT-1;
959 bfunc = lcd_remote_blockfuncs[drawmode];
960 dst = &lcd_remote_framebuffer[y1>>3][x];
961 ny = y2 - (y1 & ~7);
962 mask = 0xFFu << (y1 & 7);
963 mask_bottom = 0xFFu >> (~ny & 7);
965 for (; ny >= 8; ny -= 8)
967 bfunc(dst, mask, 0xFFu);
968 dst += LCD_REMOTE_WIDTH;
969 mask = 0xFFu;
971 mask &= mask_bottom;
972 bfunc(dst, mask, 0xFFu);
975 /* Draw a rectangular box */
976 void lcd_remote_drawrect(int x, int y, int width, int height)
978 if ((width <= 0) || (height <= 0))
979 return;
981 int x2 = x + width - 1;
982 int y2 = y + height - 1;
984 lcd_remote_vline(x, y, y2);
985 lcd_remote_vline(x2, y, y2);
986 lcd_remote_hline(x, x2, y);
987 lcd_remote_hline(x, x2, y2);
990 /* Fill a rectangular area */
991 void lcd_remote_fillrect(int x, int y, int width, int height)
993 int ny;
994 fb_remote_data *dst, *dst_end;
995 unsigned mask, mask_bottom;
996 unsigned bits = 0;
997 lcd_remote_blockfunc_type *bfunc;
998 bool fillopt = false;
1000 /* nothing to draw? */
1001 if ((width <= 0) || (height <= 0) || (x >= LCD_REMOTE_WIDTH)
1002 || (y >= LCD_REMOTE_HEIGHT) || (x + width <= 0) || (y + height <= 0))
1003 return;
1005 /* clipping */
1006 if (x < 0)
1008 width += x;
1009 x = 0;
1011 if (y < 0)
1013 height += y;
1014 y = 0;
1016 if (x + width > LCD_REMOTE_WIDTH)
1017 width = LCD_REMOTE_WIDTH - x;
1018 if (y + height > LCD_REMOTE_HEIGHT)
1019 height = LCD_REMOTE_HEIGHT - y;
1021 if (drawmode & DRMODE_INVERSEVID)
1023 if (drawmode & DRMODE_BG)
1025 fillopt = true;
1028 else
1030 if (drawmode & DRMODE_FG)
1032 fillopt = true;
1033 bits = 0xFFu;
1036 bfunc = lcd_remote_blockfuncs[drawmode];
1037 dst = &lcd_remote_framebuffer[y>>3][x];
1038 ny = height - 1 + (y & 7);
1039 mask = 0xFFu << (y & 7);
1040 mask_bottom = 0xFFu >> (~ny & 7);
1042 for (; ny >= 8; ny -= 8)
1044 if (fillopt && (mask == 0xFFu))
1045 memset(dst, bits, width);
1046 else
1048 fb_remote_data *dst_row = dst;
1050 dst_end = dst_row + width;
1052 bfunc(dst_row++, mask, 0xFFu);
1053 while (dst_row < dst_end);
1056 dst += LCD_REMOTE_WIDTH;
1057 mask = 0xFFu;
1059 mask &= mask_bottom;
1061 if (fillopt && (mask == 0xFFu))
1062 memset(dst, bits, width);
1063 else
1065 dst_end = dst + width;
1067 bfunc(dst++, mask, 0xFFu);
1068 while (dst < dst_end);
1072 /* About Rockbox' internal bitmap format:
1074 * A bitmap contains one bit for every pixel that defines if that pixel is
1075 * black (1) or white (0). Bits within a byte are arranged vertically, LSB
1076 * at top.
1077 * The bytes are stored in row-major order, with byte 0 being top left,
1078 * byte 1 2nd from left etc. The first row of bytes defines pixel rows
1079 * 0..7, the second row defines pixel row 8..15 etc.
1081 * This is the same as the internal lcd hw format. */
1083 /* Draw a partial bitmap */
1084 void lcd_remote_bitmap_part(const unsigned char *src, int src_x, int src_y,
1085 int stride, int x, int y, int width, int height)
1086 ICODE_ATTR;
1087 void lcd_remote_bitmap_part(const unsigned char *src, int src_x, int src_y,
1088 int stride, int x, int y, int width, int height)
1090 int shift, ny;
1091 fb_remote_data *dst, *dst_end;
1092 unsigned mask, mask_bottom;
1093 lcd_remote_blockfunc_type *bfunc;
1095 /* nothing to draw? */
1096 if ((width <= 0) || (height <= 0) || (x >= LCD_REMOTE_WIDTH)
1097 || (y >= LCD_REMOTE_HEIGHT) || (x + width <= 0) || (y + height <= 0))
1098 return;
1100 /* clipping */
1101 if (x < 0)
1103 width += x;
1104 src_x -= x;
1105 x = 0;
1107 if (y < 0)
1109 height += y;
1110 src_y -= y;
1111 y = 0;
1113 if (x + width > LCD_REMOTE_WIDTH)
1114 width = LCD_REMOTE_WIDTH - x;
1115 if (y + height > LCD_REMOTE_HEIGHT)
1116 height = LCD_REMOTE_HEIGHT - y;
1118 src += stride * (src_y >> 3) + src_x; /* move starting point */
1119 src_y &= 7;
1120 y -= src_y;
1121 dst = &lcd_remote_framebuffer[y>>3][x];
1122 shift = y & 7;
1123 ny = height - 1 + shift + src_y;
1125 bfunc = lcd_remote_blockfuncs[drawmode];
1126 mask = 0xFFu << (shift + src_y);
1127 mask_bottom = 0xFFu >> (~ny & 7);
1129 if (shift == 0)
1131 bool copyopt = (drawmode == DRMODE_SOLID);
1133 for (; ny >= 8; ny -= 8)
1135 if (copyopt && (mask == 0xFFu))
1136 memcpy(dst, src, width);
1137 else
1139 const unsigned char *src_row = src;
1140 fb_remote_data *dst_row = dst;
1142 dst_end = dst_row + width;
1144 bfunc(dst_row++, mask, *src_row++);
1145 while (dst_row < dst_end);
1148 src += stride;
1149 dst += LCD_REMOTE_WIDTH;
1150 mask = 0xFFu;
1152 mask &= mask_bottom;
1154 if (copyopt && (mask == 0xFFu))
1155 memcpy(dst, src, width);
1156 else
1158 dst_end = dst + width;
1160 bfunc(dst++, mask, *src++);
1161 while (dst < dst_end);
1164 else
1166 dst_end = dst + width;
1169 const unsigned char *src_col = src++;
1170 fb_remote_data *dst_col = dst++;
1171 unsigned mask_col = mask;
1172 unsigned data = 0;
1174 for (y = ny; y >= 8; y -= 8)
1176 data |= *src_col << shift;
1178 if (mask_col & 0xFFu)
1180 bfunc(dst_col, mask_col, data);
1181 mask_col = 0xFFu;
1183 else
1184 mask_col >>= 8;
1186 src_col += stride;
1187 dst_col += LCD_REMOTE_WIDTH;
1188 data >>= 8;
1190 data |= *src_col << shift;
1191 bfunc(dst_col, mask_col & mask_bottom, data);
1193 while (dst < dst_end);
1197 /* Draw a full bitmap */
1198 void lcd_remote_bitmap(const unsigned char *src, int x, int y, int width,
1199 int height)
1201 lcd_remote_bitmap_part(src, 0, 0, width, x, y, width, height);
1204 /* put a string at a given pixel position, skipping first ofs pixel columns */
1205 static void lcd_remote_putsxyofs(int x, int y, int ofs, const unsigned char *str)
1207 unsigned short ch;
1208 unsigned short *ucs;
1209 struct font* pf = font_get(curfont);
1211 ucs = bidi_l2v(str, 1);
1213 while ((ch = *ucs++) != 0 && x < LCD_REMOTE_WIDTH)
1215 int width;
1216 const unsigned char *bits;
1218 /* get proportional width and glyph bits */
1219 width = font_get_width(pf, ch);
1221 if (ofs > width)
1223 ofs -= width;
1224 continue;
1227 bits = font_get_bits(pf, ch);
1229 lcd_remote_bitmap_part(bits, ofs, 0, width, x, y, width - ofs,
1230 pf->height);
1232 x += width - ofs;
1233 ofs = 0;
1237 /* put a string at a given pixel position */
1238 void lcd_remote_putsxy(int x, int y, const unsigned char *str)
1240 lcd_remote_putsxyofs(x, y, 0, str);
1243 /*** line oriented text output ***/
1245 /* put a string at a given char position */
1246 void lcd_remote_puts(int x, int y, const unsigned char *str)
1248 lcd_remote_puts_style_offset(x, y, str, STYLE_DEFAULT, 0);
1251 void lcd_remote_puts_style(int x, int y, const unsigned char *str, int style)
1253 lcd_remote_puts_style_offset(x, y, str, style, 0);
1256 void lcd_remote_puts_offset(int x, int y, const unsigned char *str, int offset)
1258 lcd_remote_puts_style_offset(x, y, str, STYLE_DEFAULT, offset);
1261 /* put a string at a given char position, style, and pixel position,
1262 * skipping first offset pixel columns */
1263 void lcd_remote_puts_style_offset(int x, int y, const unsigned char *str,
1264 int style, int offset)
1266 int xpos,ypos,w,h,xrect;
1267 int lastmode = drawmode;
1269 /* make sure scrolling is turned off on the line we are updating */
1270 scrolling_lines &= ~(1 << y);
1272 if(!str || !str[0])
1273 return;
1275 lcd_remote_getstringsize(str, &w, &h);
1276 xpos = xmargin + x*w / utf8length((char *)str);
1277 ypos = ymargin + y*h;
1278 drawmode = (style & STYLE_INVERT) ?
1279 (DRMODE_SOLID|DRMODE_INVERSEVID) : DRMODE_SOLID;
1280 lcd_remote_putsxyofs(xpos, ypos, offset, str);
1281 drawmode ^= DRMODE_INVERSEVID;
1282 xrect = xpos + MAX(w - offset, 0);
1283 lcd_remote_fillrect(xrect, ypos, LCD_REMOTE_WIDTH - xrect, h);
1284 drawmode = lastmode;
1287 /*** scrolling ***/
1289 /* Reverse the invert setting of the scrolling line (if any) at given char
1290 position. Setting will go into affect next time line scrolls. */
1291 void lcd_remote_invertscroll(int x, int y)
1293 struct scrollinfo* s;
1295 (void)x;
1297 s = &scroll[y];
1298 s->invert = !s->invert;
1301 void lcd_remote_stop_scroll(void)
1303 scrolling_lines=0;
1306 void lcd_remote_scroll_speed(int speed)
1308 scroll_ticks = scroll_tick_table[speed];
1311 void lcd_remote_scroll_step(int step)
1313 scroll_step = step;
1316 void lcd_remote_scroll_delay(int ms)
1318 scroll_delay = ms / (HZ / 10);
1321 void lcd_remote_bidir_scroll(int percent)
1323 bidir_limit = percent;
1326 void lcd_remote_puts_scroll(int x, int y, const unsigned char *string)
1328 lcd_remote_puts_scroll_style(x, y, string, STYLE_DEFAULT);
1331 void lcd_remote_puts_scroll_style(int x, int y, const unsigned char *string, int style)
1333 lcd_remote_puts_scroll_style_offset(x, y, string, style, 0);
1336 void lcd_remote_puts_scroll_offset(int x, int y, const unsigned char *string, int offset)
1338 lcd_remote_puts_scroll_style_offset(x, y, string, STYLE_DEFAULT, offset);
1341 void lcd_remote_puts_scroll_style_offset(int x, int y, const unsigned char *string,
1342 int style, int offset)
1344 struct scrollinfo* s;
1345 int w, h;
1347 s = &scroll[y];
1349 s->start_tick = current_tick + scroll_delay;
1350 s->invert = false;
1351 if (style & STYLE_INVERT) {
1352 s->invert = true;
1353 lcd_remote_puts_style_offset(x,y,string,STYLE_INVERT,offset);
1355 else
1356 lcd_remote_puts_offset(x,y,string,offset);
1358 lcd_remote_getstringsize(string, &w, &h);
1360 if (LCD_REMOTE_WIDTH - x * 8 - xmargin < w) {
1361 /* prepare scroll line */
1362 char *end;
1364 memset(s->line, 0, sizeof s->line);
1365 strcpy(s->line, (char *)string);
1367 /* get width */
1368 s->width = lcd_remote_getstringsize((unsigned char *)s->line, &w, &h);
1370 /* scroll bidirectional or forward only depending on the string
1371 width */
1372 if ( bidir_limit ) {
1373 s->bidir = s->width < (LCD_REMOTE_WIDTH - xmargin) *
1374 (100 + bidir_limit) / 100;
1376 else
1377 s->bidir = false;
1379 if (!s->bidir) { /* add spaces if scrolling in the round */
1380 strcat(s->line, " ");
1381 /* get new width incl. spaces */
1382 s->width = lcd_remote_getstringsize((unsigned char *)s->line, &w, &h);
1385 end = strchr(s->line, '\0');
1386 strncpy(end, (char *)string, LCD_REMOTE_WIDTH/2);
1388 s->len = utf8length((char *)string);
1389 s->offset = offset;
1390 s->startx = xmargin + x * s->width / s->len;;
1391 s->backward = false;
1392 scrolling_lines |= (1<<y);
1394 else
1395 /* force a bit switch-off since it doesn't scroll */
1396 scrolling_lines &= ~(1<<y);
1399 static void scroll_thread(void)
1401 struct font* pf;
1402 struct scrollinfo* s;
1403 long next_tick = current_tick;
1404 long delay = 0;
1405 int index;
1406 int xpos, ypos;
1407 int lastmode;
1408 #ifndef SIMULATOR
1409 struct event ev;
1410 #endif
1412 /* initialize scroll struct array */
1413 scrolling_lines = 0;
1415 while ( 1 ) {
1417 #ifdef SIMULATOR
1418 sleep(delay);
1419 #else
1420 if (remote_initialized)
1421 queue_wait_w_tmo(&remote_scroll_queue, &ev, delay);
1422 else
1423 queue_wait(&remote_scroll_queue, &ev);
1425 switch (ev.id)
1427 case REMOTE_INIT_LCD:
1428 remote_lcd_init();
1429 lcd_remote_update();
1430 break;
1432 case REMOTE_DEINIT_LCD:
1433 CLK_LO;
1434 CS_HI;
1435 remote_initialized = false;
1436 break;
1439 delay = next_tick - current_tick - 1;
1440 if (delay >= 0)
1441 continue;
1442 #endif
1443 for ( index = 0; index < SCROLLABLE_LINES; index++ ) {
1444 /* really scroll? */
1445 if ( !(scrolling_lines&(1<<index)) )
1446 continue;
1448 s = &scroll[index];
1450 /* check pause */
1451 if (TIME_BEFORE(current_tick, s->start_tick))
1452 continue;
1454 if (s->backward)
1455 s->offset -= scroll_step;
1456 else
1457 s->offset += scroll_step;
1459 pf = font_get(curfont);
1460 xpos = s->startx;
1461 ypos = ymargin + index * pf->height;
1463 if (s->bidir) { /* scroll bidirectional */
1464 if (s->offset <= 0) {
1465 /* at beginning of line */
1466 s->offset = 0;
1467 s->backward = false;
1468 s->start_tick = current_tick + scroll_delay * 2;
1470 if (s->offset >= s->width - (LCD_REMOTE_WIDTH - xpos)) {
1471 /* at end of line */
1472 s->offset = s->width - (LCD_REMOTE_WIDTH - xpos);
1473 s->backward = true;
1474 s->start_tick = current_tick + scroll_delay * 2;
1477 else {
1478 /* scroll forward the whole time */
1479 if (s->offset >= s->width)
1480 s->offset %= s->width;
1483 lastmode = drawmode;
1484 drawmode = s->invert ?
1485 (DRMODE_SOLID|DRMODE_INVERSEVID) : DRMODE_SOLID;
1486 lcd_remote_putsxyofs(xpos, ypos, s->offset, s->line);
1487 drawmode = lastmode;
1488 lcd_remote_update_rect(xpos, ypos, LCD_REMOTE_WIDTH - xpos, pf->height);
1491 next_tick += scroll_ticks;
1492 delay = next_tick - current_tick - 1;
1493 if (delay < 0)
1495 next_tick = current_tick + 1;
1496 delay = 0;