rockbox/firmware/drivers/lcd-player.c

777 lines
21 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Alan Korr
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include "hwcompat.h"
#ifdef HAVE_LCD_CHARCELLS
#include "lcd.h"
#include "kernel.h"
#include "thread.h"
#include <string.h>
#include <stdlib.h>
#include "file.h"
#include "debug.h"
#include "system.h"
#include "font.h"
#include "lcd-player-charset.h"
/*** definitions ***/
#define OLD_LCD_CONTRAST_SET ((char)0xA8)
#define OLD_LCD_CRAM ((char)0xB0) /* Characters */
#define OLD_LCD_PRAM ((char)0x80) /* Patterns */
#define OLD_LCD_IRAM ((char)0xE0) /* Icons */
#define NEW_LCD_CONTRAST_SET ((char)0x50)
#define NEW_LCD_CRAM ((char)0x80) /* Characters */
#define NEW_LCD_PRAM ((char)0xC0) /* Patterns */
#define NEW_LCD_IRAM ((char)0x40) /* Icons */
#define LCD_CURSOR(x,y) ((char)(lcd_cram+((y)*16+(x))))
#define LCD_ICON(i) ((char)(lcd_iram+i))
#define SCROLLABLE_LINES 2
#define SCROLL_MODE_OFF 0
#define SCROLL_MODE_PAUSE 1
#define SCROLL_MODE_RUN 2
extern unsigned short new_lcd_rocklatin1_to_xlcd[];
extern unsigned short old_lcd_rocklatin1_to_xlcd[];
extern unsigned char lcd_player_extended_lcd_to_rocklatin1[];
extern unsigned char extended_font_player[NO_EXTENDED_LCD_CHARS][8];
/*** generic code ***/
struct scrollinfo {
int mode;
char text[MAX_PATH];
int textlen;
int offset;
int turn_offset;
int startx;
int starty;
long scroll_start_tick;
int direction; /* +1 for right or -1 for left*/
int jump_scroll;
int jump_scroll_steps;
};
#define MAX_CURSOR_CHARS 8
struct cursorinfo {
int len;
char text[MAX_CURSOR_CHARS];
int textpos;
int y_pos;
int x_pos;
int divider;
int downcount;
} cursor;
static void scroll_thread(void);
static char scroll_stack[DEFAULT_STACK_SIZE];
static char scroll_name[] = "scroll";
static char scroll_speed = 8; /* updates per second */
static int scroll_delay = HZ/2; /* delay before starting scroll */
static int jump_scroll_delay = HZ/4; /* delay between jump scroll jumps */
static char scroll_spacing = 3; /* spaces between end and start of text */
static bool allow_bidirectional_scrolling = true;
static int bidir_limit = 50; /* percent */
static int jump_scroll = 0; /* 0=off, 1=once, ..., JUMP_SCROLL_ALWAYS */
static struct scrollinfo scroll[SCROLLABLE_LINES];
static char extended_chars_mapped[NO_EXTENDED_LCD_CHARS];
static char extended_pattern_content[8]; /* Which char is mapped in pattern */
static char extended_pattern_usage[8]; /* Counting number of times used */
static char pattern_size; /* Last pattern, 3 for old LCD, 7 for new LCD */
static bool new_lcd;
unsigned short *lcd_ascii;
static char lcd_contrast_set;
static char lcd_cram;
static char lcd_pram;
static char lcd_iram;
unsigned short buffer_xlcd[11][2];
unsigned short buffer_lcd_mirror[11][2];
#ifdef SIMULATOR
unsigned char hardware_buffer_lcd[11][2];
#else
static unsigned char lcd_data_byte; /* global write buffer */
#endif
#define NO_CHAR -1
static void lcd_free_pat(int map_ch)
{
int x, y;
unsigned char substitute_char;
int pat;
pat=extended_chars_mapped[map_ch];
if (pat!=NO_CHAR) {
substitute_char=lcd_player_extended_lcd_to_rocklatin1[map_ch];
/* TODO: use a define for the screen width! */
for (x=0; x<11; x++) {
/* TODO: use a define for the screen height! */
for (y=0; y<2; y++) {
if (map_ch==lcd_ascii[buffer_xlcd[x][y]]-512) {
buffer_xlcd[x][y]=substitute_char;
buffer_lcd_mirror[x][y]=substitute_char;
#ifdef SIMULATOR
hardware_buffer_lcd[x][y]=substitute_char;
#else
lcd_write_command(LCD_CURSOR(x, y));
lcd_write_data(&substitute_char, 1);
#endif
}
}
}
extended_chars_mapped[map_ch]=NO_CHAR;
extended_pattern_content[pat]=NO_CHAR;
extended_pattern_usage[pat]=0;
}
#ifdef SIMULATOR
lcd_update();
#endif
}
static int lcd_get_free_pat(int ch)
{
int pat;
int last_pat=0;
static int last_used_pat=0;
int loop;
pat=last_used_pat;
for (loop=0; loop<=pattern_size; loop++) {
pat=(pat+1)&pattern_size; /* Keep 'pat' within limits */
if (extended_pattern_usage[pat]==0) {
int map_ch=extended_pattern_content[pat];
if (map_ch != NO_CHAR) {
extended_chars_mapped[map_ch]=NO_CHAR;
extended_pattern_content[pat]=NO_CHAR;
}
last_used_pat=pat;
return pat;
}
if (extended_pattern_content[pat]>extended_pattern_content[last_pat])
last_pat=pat;
}
if (ch<32) { /* Prioritized char */
/* Remove last_pat */
lcd_free_pat(extended_pattern_content[last_pat]);
last_used_pat=last_pat;
return last_pat;
}
return NO_CHAR;
}
void xlcd_update(void)
{
int x, y;
for (x=0; x<11; x++) {
for (y=0; y<2; y++) {
unsigned short ch=buffer_xlcd[x][y];
unsigned char hw_ch=0xff;
if (ch==buffer_lcd_mirror[x][y])
continue; /* No need to redraw */
buffer_lcd_mirror[x][y]=ch;
if (ch>=256 && ch<512) {
hw_ch=ch-256;
} else {
int map_ch=lcd_ascii[ch];
if (map_ch<512) {
hw_ch=map_ch;
}
else {
map_ch=map_ch-512;
if (extended_chars_mapped[map_ch]!=NO_CHAR) {
hw_ch=extended_chars_mapped[map_ch];
extended_pattern_usage[hw_ch]++;
}
else {
int pat;
pat=lcd_get_free_pat(map_ch);
if (pat<0) {
DEBUGF("Substitute for %02x (map 0x%02x)"
" is used.\n", ch, map_ch);
/* Find substitute char */
map_ch=
lcd_player_extended_lcd_to_rocklatin1[map_ch];
hw_ch=lcd_ascii[map_ch];
} else {
#ifdef DEBUG
if (extended_pattern_usage[pat]!=0) {
DEBUGF("***Pattern %d is not zero!\n", pat);
}
#endif
extended_chars_mapped[map_ch]=pat;
extended_pattern_content[pat]=map_ch;
extended_pattern_usage[pat]=1;
lcd_define_hw_pattern(pat*8,
extended_font_player[map_ch],
8);
hw_ch=pat;
}
}
}
}
#ifdef SIMULATOR
hardware_buffer_lcd[x][y]=hw_ch;
#else
lcd_write_command(LCD_CURSOR(x,y));
lcd_write_data(&hw_ch, 1);
#endif
}
}
lcd_update();
}
bool lcdx_putc(int x, int y, unsigned short ch)
{
int lcd_char;
if (buffer_xlcd[x][y]==ch)
return false; /* Same char, ignore any update */
lcd_char=lcd_ascii[buffer_xlcd[x][y]];
if (lcd_char>=512) {
/* The removed char is a defined pattern, count down the reference. */
extended_pattern_usage[(int)extended_chars_mapped[lcd_char-512]]--;
#ifdef DEBUG
if (extended_pattern_usage[(int)extended_chars_mapped[lcd_char]]<0) {
DEBUGF("**** Mapped char %02x is less than 0!\n", lcd_char);
}
#endif
}
buffer_xlcd[x][y]=ch;
lcd_char=lcd_ascii[ch];
if (lcd_char>=256)
return true; /* Caller shall call xlcd_update() when done */
buffer_lcd_mirror[x][y]=lcd_char;
#ifdef SIMULATOR
hardware_buffer_lcd[x][y]=lcd_char;
#else
lcd_data_byte = (unsigned char) lcd_char;
lcd_write_command(LCD_CURSOR(x, y));
lcd_write_data(&lcd_data_byte, 1);
#endif
return false;
}
int lcd_default_contrast(void)
{
return 30;
}
void lcd_clear_display(void)
{
int i;
bool update=false;
DEBUGF("lcd_clear_display()\n");
lcd_stop_scroll();
cursor.len=0; /* Stop cursor */
for (i=0;i<22;i++)
update|=lcdx_putc(i%11, i/11, ' ');
if (update)
xlcd_update();
}
static void lcd_puts_cont_scroll(int x, int y, unsigned char *string)
{
bool update=false;
DEBUGF("lcd_puts_cont_scroll(%d, %d, \"", x, y);
for (; *string && x<11; x++)
{
#ifdef DEBUGF
if (*string>=32 && *string<128)
{
DEBUGF("%c", *string);
}
else
{
DEBUGF("(0x%02x)", *string);
}
#endif
/* We should check if char is over 256 */
update|=lcdx_putc(x, y, *(unsigned char*)string++);
}
DEBUGF("\")\n");
for (; x<11; x++)
update|=lcdx_putc(x, y, ' ');
if (update)
xlcd_update();
#ifdef SIMULATOR
lcd_update();
#endif
}
void lcd_puts(int x, int y, unsigned char *string)
{
DEBUGF("lcd_puts(%d, %d) -> ", x, y);
scroll[y].mode=SCROLL_MODE_OFF;
return lcd_puts_cont_scroll(x, y, string);
}
void lcd_put_cursor(int x, int y, char cursor_char)
{
if (cursor.len == 0) {
cursor.text[0]=buffer_xlcd[x][y];
cursor.text[1]=cursor_char;
cursor.len=2;
cursor.textpos=0;
cursor.y_pos=y;
cursor.x_pos=x;
cursor.downcount=0;
cursor.divider=4;
}
}
void lcd_remove_cursor(void)
{
if (cursor.len!=0) {
bool up;
cursor.len=0;
up = lcdx_putc(cursor.x_pos, cursor.y_pos, cursor.text[0]);
#ifdef SIMULATOR
if(up)
lcd_update();
#endif
}
}
void lcd_putc(int x, int y, unsigned short ch)
{
bool update;
DEBUGF("lcd_putc(%d, %d, %d '0x%02x')\n", x, y, ch, ch);
if (x<0 || y<0) {
return;
}
update=lcdx_putc(x, y, ch);
if (update)
xlcd_update();
}
unsigned char lcd_get_locked_pattern(void)
{
unsigned char pat=1;
while (pat<LAST_RESERVED_CHAR) {
if (lcd_ascii[pat]==RESERVED_CHAR) {
lcd_ascii[pat]=0x200+pat;
return pat;
}
pat++;
}
return 0;
}
void lcd_unlock_pattern(unsigned char pat)
{
lcd_ascii[pat]=RESERVED_CHAR;
lcd_free_pat(pat);
}
void lcd_define_pattern(int pat, char *pattern)
{
int i;
for (i=0; i<7; i++) {
extended_font_player[pat][i]=pattern[i];
}
if (extended_chars_mapped[pat]!=NO_CHAR) {
lcd_define_hw_pattern(extended_chars_mapped[pat]*8, pattern, 7);
}
}
#ifndef SIMULATOR
void lcd_define_hw_pattern (int which,char *pattern,int length)
{
lcd_write_command(lcd_pram | which);
lcd_write_data(pattern, length);
}
void lcd_double_height(bool on)
{
if(new_lcd)
lcd_write_command(on?9:8);
}
static char icon_pos[] =
{
0, 0, 0, 0, /* Battery */
2, /* USB */
3, /* Play */
4, /* Record */
5, /* Pause */
5, /* Audio */
6, /* Repeat */
7, /* 1 */
9, /* Volume */
9, /* Volume 1 */
9, /* Volume 2 */
10, /* Volume 3 */
10, /* Volume 4 */
10, /* Volume 5 */
10, /* Param */
};
static char icon_mask[] =
{
0x02, 0x08, 0x04, 0x10, /* Battery */
0x04, /* USB */
0x10, /* Play */
0x10, /* Record */
0x02, /* Pause */
0x10, /* Audio */
0x02, /* Repeat */
0x01, /* 1 */
0x04, /* Volume */
0x02, /* Volume 1 */
0x01, /* Volume 2 */
0x08, /* Volume 3 */
0x04, /* Volume 4 */
0x01, /* Volume 5 */
0x10, /* Param */
};
void lcd_icon(int icon, bool enable)
{
static unsigned char icon_mirror[11] = {0};
int pos, mask;
pos = icon_pos[icon];
mask = icon_mask[icon];
lcd_write_command(LCD_ICON(pos));
if(enable)
icon_mirror[pos] |= mask;
else
icon_mirror[pos] &= ~mask;
lcd_write_data(&icon_mirror[pos], 1);
}
void lcd_set_contrast(int val)
{
lcd_data_byte = (unsigned char) (31 - val);
lcd_write_command(lcd_contrast_set);
lcd_write_data(&lcd_data_byte, 1);
}
#endif /* SIMULATOR */
void lcd_init (void)
{
#ifdef HAVE_NEO_LCD
new_lcd = true;
#else
new_lcd = has_new_lcd();
#endif
memset(extended_chars_mapped, NO_CHAR, sizeof(extended_chars_mapped));
memset(extended_pattern_content, NO_CHAR,sizeof(extended_pattern_content));
memset(extended_pattern_usage, 0, sizeof(extended_pattern_usage));
if(new_lcd) {
lcd_ascii = new_lcd_rocklatin1_to_xlcd;
lcd_contrast_set = NEW_LCD_CONTRAST_SET;
lcd_cram = NEW_LCD_CRAM;
lcd_pram = NEW_LCD_PRAM;
lcd_iram = NEW_LCD_IRAM;
pattern_size=7; /* Last pattern, 7 for new LCD */
}
else {
lcd_ascii = old_lcd_rocklatin1_to_xlcd;
lcd_contrast_set = OLD_LCD_CONTRAST_SET;
lcd_cram = OLD_LCD_CRAM;
lcd_pram = OLD_LCD_PRAM;
lcd_iram = OLD_LCD_IRAM;
pattern_size=3; /* Last pattern, 3 for old LCD */
}
lcd_set_contrast(lcd_default_contrast());
create_thread(scroll_thread, scroll_stack,
sizeof(scroll_stack), scroll_name);
}
void lcd_jump_scroll (int mode) /* 0=off, 1=once, ..., JUMP_SCROLL_ALWAYS */
{
jump_scroll=mode;
}
void lcd_bidir_scroll(int percent)
{
bidir_limit = percent;
}
void lcd_puts_scroll(int x, int y, unsigned char* string )
{
struct scrollinfo* s;
int i;
DEBUGF("lcd_puts_scroll(%d, %d, %s)\n", x, y, string);
s = &scroll[y];
lcd_puts_cont_scroll(x,y,string);
s->textlen = strlen(string);
if ( s->textlen > 11-x ) {
s->mode = SCROLL_MODE_RUN;
s->scroll_start_tick = current_tick + scroll_delay;
s->offset=0;
s->startx=x;
s->starty=y;
s->direction=+1;
s->jump_scroll=0;
s->jump_scroll_steps=0;
if (jump_scroll && jump_scroll_delay<(HZ/scroll_speed)*(s->textlen-11+x)) {
s->jump_scroll_steps=11-x;
s->jump_scroll=jump_scroll;
}
strncpy(s->text,string,sizeof s->text);
s->turn_offset=-1;
if (bidir_limit && (s->textlen < ((11-x)*(100+bidir_limit))/100)) {
s->turn_offset=s->textlen+x-11;
}
else {
for (i=0; i<scroll_spacing &&
s->textlen<(int)sizeof(s->text); i++) {
s->text[s->textlen++]=' ';
}
}
if (s->textlen<(int)sizeof(s->text))
s->text[s->textlen]=' ';
s->text[sizeof s->text - 1] = 0;
}
else
s->mode = SCROLL_MODE_OFF;
}
void lcd_stop_scroll(void)
{
struct scrollinfo* s;
int index;
for ( index = 0; index < SCROLLABLE_LINES; index++ ) {
s = &scroll[index];
if ( s->mode == SCROLL_MODE_RUN ||
s->mode == SCROLL_MODE_PAUSE ) {
/* restore scrolled row */
lcd_puts(s->startx, s->starty, s->text);
}
}
lcd_update();
}
void lcd_allow_bidirectional_scrolling(bool on)
{
allow_bidirectional_scrolling=on;
}
void lcd_scroll_speed(int speed)
{
scroll_speed = speed;
}
void lcd_scroll_delay(int ms)
{
scroll_delay = ms / (HZ / 10);
DEBUGF("scroll_delay=%d (ms=%d, HZ=%d)\n", scroll_delay, ms, HZ);
}
void lcd_jump_scroll_delay(int ms)
{
jump_scroll_delay = ms / (HZ / 10);
DEBUGF("jump_scroll_delay=%d (ms=%d, HZ=%d)\n", jump_scroll_delay, ms, HZ);
}
static void scroll_thread(void)
{
struct scrollinfo* s;
int index;
int i, o;
bool update;
/* initialize scroll struct array */
for (index = 0; index < SCROLLABLE_LINES; index++) {
scroll[index].mode = SCROLL_MODE_OFF;
}
while ( 1 ) {
update = false;
for ( index = 0; index < SCROLLABLE_LINES; index++ ) {
s = &scroll[index];
if ( s->mode == SCROLL_MODE_RUN ) {
if ( TIME_AFTER(current_tick, s->scroll_start_tick) ) {
char buffer[12];
int jumping_scroll=s->jump_scroll;
update = true;
if (s->jump_scroll) {
/* Find new position to start jump scroll by
* finding last white space within
* jump_scroll_steps */
int i;
o = s->offset = s->offset + s->jump_scroll_steps;
for (i = 0; i < s->jump_scroll_steps; i++, o--) {
if (o < s->textlen &&
((0x20 <= s->text[o] && s->text[o] <= 0x2f) || s->text[o] == '_'))
{
s->offset = o;
break;
}
}
s->scroll_start_tick = current_tick +
jump_scroll_delay;
/* Eat space */
while (s->offset < s->textlen &&
((0x20 <= s->text[s->offset] && s->text[s->offset] <= 0x2f) ||
s->text[s->offset] == '_')) {
s->offset++;
}
if (s->offset >= s->textlen) {
s->offset=0;
s->scroll_start_tick = current_tick +
scroll_delay;
if (s->jump_scroll != JUMP_SCROLL_ALWAYS) {
s->jump_scroll--;
s->direction=1;
}
}
} else {
if ( s->offset < s->textlen-1 ) {
s->offset+=s->direction;
if (s->offset==0) {
s->direction=+1;
s->scroll_start_tick = current_tick +
scroll_delay;
} else {
if (s->offset == s->turn_offset) {
s->direction=-1;
s->scroll_start_tick = current_tick +
scroll_delay;
}
}
}
else {
s->offset = 0;
}
}
i=0;
o=s->offset;
while (i<11) {
buffer[i++]=s->text[o++];
if (o==s->textlen /* || (jump_scroll && buffer[i-1] == ' ') */)
break;
}
o=0;
if (s->turn_offset == -1 && !jumping_scroll) {
while (i<11) {
buffer[i++]=s->text[o++];
}
} else {
while (i<11) {
buffer[i++]=' ';
}
}
buffer[11]=0;
lcd_puts_cont_scroll(s->startx, s->starty, buffer);
}
}
if (cursor.len>0) {
if (cursor.downcount--<0) {
cursor.downcount=cursor.divider;
cursor.textpos++;
if (cursor.textpos>=cursor.len)
cursor.textpos=0;
update|=lcdx_putc(cursor.x_pos, cursor.y_pos,
cursor.text[cursor.textpos]);
}
}
if (update) {
lcd_update();
}
}
sleep(HZ/scroll_speed);
}
}
#ifdef HAVE_NEO_LCD
/*
* Function use by the Neo code, but could/should be made a generic one.
*/
void lcd_cursor(int x, int y)
{
/* If we make sure the display size is setup with proper defines in the
config-*.h files, this should work on all displays */
if ((cursor.y_pos==y && cursor.x_pos==x) ||
x>=20 ||
y>3 ||
x<0 ||
y<0) {
DEBUGF("ignoring request for cursor to %d,%d - currently %d,%d\n",
x,y,cursor.x_pos,cursor.y_pos);
return;
}
char value=0;
cursor.y_pos=y;
cursor.x_pos=x;
switch (y) {
case 0:
value=0x80|x;
break;
case 1:
value=0x80|(x+0x40);
break;
case 2:
value=0x80|(x+0x14);
break;
case 3:
value=0x80|(x+0x54);
break;
}
lcd_write_command(value);
}
#endif
#endif /* HAVE_LCD_CHARCELLS */