/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007 by Michael Sevakis * * LCD scrolling driver and scheduler * * Much collected and combined from the various Rockbox LCD drivers. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" #include "gcc_extensions.h" #include "cpu.h" #include "kernel.h" #include "thread.h" #include "usb.h" #include "lcd.h" #include "font.h" #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif #include "scroll_engine.h" static const char scroll_tick_table[16] = { /* Hz values: 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33 */ 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3 }; static void scroll_thread(void); static char scroll_stack[DEFAULT_STACK_SIZE*3]; static const char scroll_name[] = "scroll"; static struct scrollinfo lcd_scroll[LCD_SCROLLABLE_LINES]; #ifdef HAVE_REMOTE_LCD static struct scrollinfo lcd_remote_scroll[LCD_REMOTE_SCROLLABLE_LINES]; static struct event_queue scroll_queue SHAREDBSS_ATTR; #endif struct scroll_screen_info lcd_scroll_info = { .scroll = lcd_scroll, .lines = 0, .ticks = 12, .delay = HZ/2, .bidir_limit = 50, #ifdef HAVE_LCD_BITMAP .step = 6, #endif #ifdef HAVE_LCD_CHARCELLS .jump_scroll_delay = HZ/4, .jump_scroll = 0, #endif }; #ifdef HAVE_REMOTE_LCD struct scroll_screen_info lcd_remote_scroll_info = { .scroll = lcd_remote_scroll, .lines = 0, .ticks = 12, .delay = HZ/2, .bidir_limit = 50, .step = 6, }; #endif /* HAVE_REMOTE_LCD */ void lcd_stop_scroll(void) { lcd_scroll_info.lines = 0; } /* Stop scrolling line y in the specified viewport, or all lines if y < 0 */ void lcd_scroll_stop_line(const struct viewport* current_vp, int y) { int i = 0; while (i < lcd_scroll_info.lines) { if ((lcd_scroll_info.scroll[i].vp == current_vp) && ((y < 0) || (lcd_scroll_info.scroll[i].y == y))) { /* If i is not the last active line in the array, then move the last item to position i */ if ((i + 1) != lcd_scroll_info.lines) { lcd_scroll_info.scroll[i] = lcd_scroll_info.scroll[lcd_scroll_info.lines-1]; } lcd_scroll_info.lines--; /* A line can only appear once, so we're done, * unless we are clearing the whole viewport */ if (y >= 0) return ; } else { i++; } } } /* Stop all scrolling lines in the specified viewport */ void lcd_scroll_stop(const struct viewport* vp) { lcd_scroll_stop_line(vp, -1); } void lcd_scroll_speed(int speed) { lcd_scroll_info.ticks = scroll_tick_table[speed]; } #if defined(HAVE_LCD_BITMAP) void lcd_scroll_step(int step) { lcd_scroll_info.step = step; } #endif void lcd_scroll_delay(int ms) { lcd_scroll_info.delay = ms / (HZ / 10); } void lcd_bidir_scroll(int percent) { lcd_scroll_info.bidir_limit = percent; } #ifdef HAVE_LCD_CHARCELLS void lcd_jump_scroll(int mode) /* 0=off, 1=once, ..., JUMP_SCROLL_ALWAYS */ { lcd_scroll_info.jump_scroll = mode; } void lcd_jump_scroll_delay(int ms) { lcd_scroll_info.jump_scroll_delay = ms / (HZ / 10); } #endif #ifdef HAVE_REMOTE_LCD void lcd_remote_stop_scroll(void) { lcd_remote_scroll_info.lines = 0; } /* Stop scrolling line y in the specified viewport, or all lines if y < 0 */ void lcd_remote_scroll_stop_line(const struct viewport* current_vp, int y) { int i = 0; while (i < lcd_remote_scroll_info.lines) { if ((lcd_remote_scroll_info.scroll[i].vp == current_vp) && ((y < 0) || (lcd_remote_scroll_info.scroll[i].y == y))) { /* If i is not the last active line in the array, then move the last item to position i */ if ((i + 1) != lcd_remote_scroll_info.lines) { lcd_remote_scroll_info.scroll[i] = lcd_remote_scroll_info.scroll[lcd_remote_scroll_info.lines-1]; } lcd_remote_scroll_info.lines--; /* A line can only appear once, so we're done, * unless we are clearing the whole viewport */ if (y >= 0) return ; } else { i++; } } } /* Stop all scrolling lines in the specified viewport */ void lcd_remote_scroll_stop(const struct viewport* vp) { lcd_remote_scroll_stop_line(vp, -1); } void lcd_remote_scroll_speed(int speed) { lcd_remote_scroll_info.ticks = scroll_tick_table[speed]; } void lcd_remote_scroll_step(int step) { lcd_remote_scroll_info.step = step; } void lcd_remote_scroll_delay(int ms) { lcd_remote_scroll_info.delay = ms / (HZ / 10); } void lcd_remote_bidir_scroll(int percent) { lcd_remote_scroll_info.bidir_limit = percent; } static void sync_display_ticks(void) { lcd_scroll_info.last_scroll = lcd_remote_scroll_info.last_scroll = current_tick; } static bool scroll_process_message(int delay) { struct queue_event ev; do { long tick = current_tick; queue_wait_w_tmo(&scroll_queue, &ev, delay); switch (ev.id) { case SYS_TIMEOUT: return false; case SYS_USB_CONNECTED: usb_acknowledge(SYS_USB_CONNECTED_ACK); usb_wait_for_disconnect(&scroll_queue); sync_display_ticks(); return true; #if (CONFIG_PLATFORM & PLATFORM_NATIVE) case SYS_REMOTE_PLUGGED: if (!remote_initialized) sync_display_ticks(); #endif } delay -= current_tick - tick; } while (delay > 0); return false; } #endif /* HAVE_REMOTE_LCD */ static void scroll_thread(void) NORETURN_ATTR; #ifdef HAVE_REMOTE_LCD static void scroll_thread(void) { enum { SCROLL_LCD = 0x1, SCROLL_LCD_REMOTE = 0x2, }; sync_display_ticks(); while ( 1 ) { long delay; int scroll; long tick_lcd, tick_remote; tick_lcd = lcd_scroll_info.last_scroll + lcd_scroll_info.ticks; delay = current_tick; if ( #if (CONFIG_PLATFORM & PLATFORM_NATIVE) !remote_initialized || #endif (tick_remote = lcd_remote_scroll_info.last_scroll + lcd_remote_scroll_info.ticks, TIME_BEFORE(tick_lcd, tick_remote))) { scroll = SCROLL_LCD; delay = tick_lcd - delay; } /* TIME_BEFORE(tick_remote, tick_lcd) */ else if (tick_lcd != tick_remote) { scroll = SCROLL_LCD_REMOTE; delay = tick_remote - delay; } else { scroll = SCROLL_LCD | SCROLL_LCD_REMOTE; delay = tick_lcd - delay; } if (scroll_process_message(delay)) continue; if (scroll & SCROLL_LCD) { #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) if (lcd_active()) #endif lcd_scroll_fn(); lcd_scroll_info.last_scroll = current_tick; } if (scroll == (SCROLL_LCD | SCROLL_LCD_REMOTE)) yield(); if (scroll & SCROLL_LCD_REMOTE) { lcd_remote_scroll_fn(); lcd_remote_scroll_info.last_scroll = current_tick; } } } #else static void scroll_thread(void) { while (1) { sleep(lcd_scroll_info.ticks); #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) if (lcd_active()) #endif lcd_scroll_fn(); } } #endif /* HAVE_REMOTE_LCD */ void scroll_init(void) { #ifdef HAVE_REMOTE_LCD queue_init(&scroll_queue, true); #endif create_thread(scroll_thread, scroll_stack, sizeof(scroll_stack), 0, scroll_name IF_PRIO(, PRIORITY_USER_INTERFACE) IF_COP(, CPU)); }