adb978a44d
Fix CGU_DBOP setting Set PCLK to the exact frequency (62MHz, not the maximal frequency) Use a better comment for CLK_DIV macro Use preprocessor safety checks for clock divider sizes to avoid future mistakes (not for SD_IDENT frequency since that check is handled by mci_set_clock_divider) Use maximal IDE frequency of 66MHz (like OF), not 90MHz like written in AS3525 datasheet. The IDE chip is somehow linked to internal storage, and a too high frequency could affect the storage driver. Use the same DBOP frequency of 32MHz for all models (like OF, verified clip, fuze, e200v2 and m200v4), compromise between performance and battery life could be added in the future for each target Performance increase on Sansa Fuze with DBOP freq. set to 64MHz: +12% fps for lcd_update, +1% fps for yuv Thanks to daytona955 on the forums for his help git-svn-id: svn://svn.rockbox.org/rockbox/trunk@20923 a1c6a512-1295-4272-9138-f99709370657
403 lines
10 KiB
C
403 lines
10 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2008 by Dave Chapman
|
|
*
|
|
* LCD driver for the Sansa Fuze - controller unknown
|
|
*
|
|
* 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 "cpu.h"
|
|
#include "lcd.h"
|
|
#include "file.h"
|
|
#include "debug.h"
|
|
#include "system.h"
|
|
#include "clock-target.h"
|
|
|
|
/* The controller is unknown, but some registers appear to be the same as the
|
|
HD66789R */
|
|
|
|
#define R_ENTRY_MODE 0x03
|
|
#define R_RAM_ADDR_SET 0x21
|
|
#define R_WRITE_DATA_2_GRAM 0x22
|
|
|
|
#define R_ENTRY_MODE_HORZ 0x7030
|
|
#define R_ENTRY_MODE_VERT 0x7038
|
|
|
|
static unsigned lcd_yuv_options SHAREDBSS_ATTR = 0;
|
|
static bool display_on = false; /* is the display turned on? */
|
|
static bool display_flipped = false;
|
|
static int xoffset = 20; /* needed for flip */
|
|
/* we need to write a red pixel for correct button reads
|
|
* (see lcd_button_support()), but that must not happen while the lcd is updating
|
|
* so block lcd_button_support the during updates */
|
|
static bool lcd_busy = false;
|
|
|
|
static void as3525_dbop_init(void)
|
|
{
|
|
CGU_DBOP = (1<<3) | (CLK_DIV(AS3525_PCLK_FREQ, AS3525_DBOP_FREQ) - 1);
|
|
|
|
DBOP_TIMPOL_01 = 0xe167e167;
|
|
DBOP_TIMPOL_23 = 0xe167006e;
|
|
DBOP_CTRL = 0x41008;
|
|
|
|
GPIOB_AFSEL = 0xfc;
|
|
GPIOC_AFSEL = 0xff;
|
|
|
|
DBOP_TIMPOL_23 = 0x6000e;
|
|
DBOP_CTRL = 0x51008;
|
|
DBOP_TIMPOL_01 = 0x6e167;
|
|
DBOP_TIMPOL_23 = 0xa167e06f;
|
|
|
|
/* TODO: The OF calls some other functions here, but maybe not important */
|
|
}
|
|
|
|
static void lcd_write_cmd(int cmd)
|
|
{
|
|
int x;
|
|
|
|
/* Write register */
|
|
DBOP_CTRL &= ~(1<<14);
|
|
|
|
DBOP_TIMPOL_23 = 0xa167006e;
|
|
|
|
DBOP_DOUT = cmd;
|
|
|
|
/* Wait for fifo to empty */
|
|
while ((DBOP_STAT & (1<<10)) == 0);
|
|
|
|
/* This loop is unique to the Fuze */
|
|
x = 0;
|
|
do {
|
|
asm volatile ("nop\n");
|
|
} while (x++ < 4);
|
|
|
|
|
|
DBOP_TIMPOL_23 = 0xa167e06f;
|
|
}
|
|
|
|
void lcd_write_data(const fb_data* p_bytes, int count)
|
|
{
|
|
while (count--)
|
|
{
|
|
DBOP_DOUT = *p_bytes++;
|
|
|
|
/* Wait for fifo to empty */
|
|
while ((DBOP_STAT & (1<<10)) == 0);
|
|
}
|
|
}
|
|
|
|
static void lcd_write_reg(int reg, int value)
|
|
{
|
|
unsigned short data = value;
|
|
|
|
lcd_write_cmd(reg);
|
|
lcd_write_data(&data, 1);
|
|
}
|
|
|
|
/* turn the display upside down (call lcd_update() afterwards) */
|
|
void lcd_set_flip(bool yesno)
|
|
{
|
|
display_flipped = yesno;
|
|
xoffset = yesno ? 0 : 20; /* TODO: Implement flipped mode */
|
|
|
|
/* TODO */
|
|
}
|
|
|
|
|
|
static void _display_on(void)
|
|
{
|
|
/* Initialise in the same way as the original firmare */
|
|
|
|
lcd_write_reg(0x07, 0);
|
|
lcd_write_reg(0x13, 0);
|
|
|
|
lcd_write_reg(0x11, 0x3704);
|
|
lcd_write_reg(0x14, 0x1a1b);
|
|
lcd_write_reg(0x10, 0x3860);
|
|
lcd_write_reg(0x13, 0x40);
|
|
|
|
lcd_write_reg(0x13, 0x60);
|
|
|
|
lcd_write_reg(0x13, 0x70);
|
|
lcd_write_reg(0x01, 277);
|
|
lcd_write_reg(0x02, (7<<8));
|
|
lcd_write_reg(R_ENTRY_MODE, R_ENTRY_MODE_HORZ);
|
|
lcd_write_reg(0x08, 0x01);
|
|
lcd_write_reg(0x0b, (1<<10));
|
|
lcd_write_reg(0x0c, 0);
|
|
|
|
lcd_write_reg(0x30, 0x40);
|
|
lcd_write_reg(0x31, 0x0687);
|
|
lcd_write_reg(0x32, 0x0306);
|
|
lcd_write_reg(0x33, 0x104);
|
|
lcd_write_reg(0x34, 0x0585);
|
|
lcd_write_reg(0x35, 255+66);
|
|
lcd_write_reg(0x36, 0x0687+128);
|
|
lcd_write_reg(0x37, 259);
|
|
lcd_write_reg(0x38, 0);
|
|
lcd_write_reg(0x39, 0);
|
|
|
|
lcd_write_reg(0x42, (LCD_WIDTH - 1));
|
|
lcd_write_reg(0x43, 0);
|
|
lcd_write_reg(0x44, (LCD_WIDTH - 1));
|
|
lcd_write_reg(0x45, 0);
|
|
lcd_write_reg(0x46, (((LCD_WIDTH - 1) + xoffset) << 8) | xoffset);
|
|
lcd_write_reg(0x47, (LCD_HEIGHT - 1));
|
|
lcd_write_reg(0x48, 0x0);
|
|
|
|
lcd_write_reg(0x07, 0x11);
|
|
lcd_write_reg(0x07, 0x17);
|
|
|
|
display_on = true; /* must be done before calling lcd_update() */
|
|
lcd_update();
|
|
}
|
|
|
|
#if defined(HAVE_LCD_ENABLE)
|
|
void lcd_enable(bool on)
|
|
{
|
|
if (display_on == on)
|
|
return; /* nothing to do */
|
|
if(on)
|
|
{
|
|
int delay = 0x200000;
|
|
lcd_write_reg(0, 1);
|
|
lcd_write_reg(0x10, 0);
|
|
lcd_write_reg(0x11, 0x3704);
|
|
lcd_write_reg(0x14, 0x1a1b);
|
|
lcd_write_reg(0x10, 0x3860);
|
|
lcd_write_reg(0x13, 0x40);
|
|
lcd_write_reg(0x13, 0x60);
|
|
lcd_write_reg(0x13, 112);
|
|
lcd_write_reg(0x07, 0x11);
|
|
lcd_write_reg(0x07, 0x17);
|
|
display_on = true;
|
|
/* a bit of delay before returning to
|
|
* avoid irritating flash on backlight on */
|
|
while(delay--);
|
|
lcd_activation_call_hook();
|
|
|
|
}
|
|
else
|
|
{
|
|
lcd_write_reg(0x07, 0x22);
|
|
lcd_write_reg(0x07, 0);
|
|
lcd_write_reg(0x10, 1);
|
|
display_on = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP)
|
|
bool lcd_active(void)
|
|
{
|
|
return display_on;
|
|
}
|
|
#endif
|
|
|
|
/*** update functions ***/
|
|
|
|
/* Set horizontal window addresses */
|
|
static void lcd_window_x(int xmin, int xmax)
|
|
{
|
|
xmin += xoffset;
|
|
xmax += xoffset;
|
|
lcd_write_reg(0x46, (xmax << 8) | xmin);
|
|
lcd_write_reg(0x20, xmin);
|
|
}
|
|
|
|
/* Set vertical window addresses */
|
|
static void lcd_window_y(int ymin, int ymax)
|
|
{
|
|
lcd_write_reg(0x47, ymax);
|
|
lcd_write_reg(0x48, ymin);
|
|
lcd_write_reg(0x21, ymin);
|
|
}
|
|
|
|
void lcd_yuv_set_options(unsigned options)
|
|
{
|
|
lcd_yuv_options = options;
|
|
}
|
|
|
|
/* Line write helper function for lcd_yuv_blit. Write two lines of yuv420. */
|
|
extern void lcd_write_yuv420_lines(unsigned char const * const src[3],
|
|
int width,
|
|
int stride);
|
|
extern void lcd_write_yuv420_lines_odither(unsigned char const * const src[3],
|
|
int width,
|
|
int stride,
|
|
int x_screen, /* To align dither pattern */
|
|
int y_screen);
|
|
/* Performance function to blit a YUV bitmap directly to the LCD */
|
|
void lcd_blit_yuv(unsigned char * const src[3],
|
|
int src_x, int src_y, int stride,
|
|
int x, int y, int width, int height)
|
|
{
|
|
unsigned char const * yuv_src[3];
|
|
off_t z;
|
|
|
|
lcd_busy = true;
|
|
|
|
lcd_write_reg(R_ENTRY_MODE, R_ENTRY_MODE_VERT);
|
|
|
|
/* Sorry, but width and height must be >= 2 or else */
|
|
width &= ~1;
|
|
height >>= 1;
|
|
|
|
z = stride*src_y;
|
|
yuv_src[0] = src[0] + z + src_x;
|
|
yuv_src[1] = src[1] + (z >> 2) + (src_x >> 1);
|
|
yuv_src[2] = src[2] + (yuv_src[1] - src[1]);
|
|
|
|
lcd_window_x(x, x + width - 1);
|
|
|
|
if (lcd_yuv_options & LCD_YUV_DITHER)
|
|
{
|
|
do
|
|
{
|
|
lcd_window_y(y, y + 1);
|
|
/* Start write to GRAM */
|
|
lcd_write_cmd(R_WRITE_DATA_2_GRAM);
|
|
|
|
lcd_write_yuv420_lines_odither(yuv_src, width, stride, x, y);
|
|
yuv_src[0] += stride << 1; /* Skip down two luma lines */
|
|
yuv_src[1] += stride >> 1; /* Skip down one chroma line */
|
|
yuv_src[2] += stride >> 1;
|
|
y += 2;
|
|
}
|
|
while (--height > 0);
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
lcd_window_y(y, y + 1);
|
|
/* Start write to GRAM */
|
|
lcd_write_cmd(R_WRITE_DATA_2_GRAM);
|
|
|
|
lcd_write_yuv420_lines(yuv_src, width, stride);
|
|
yuv_src[0] += stride << 1; /* Skip down two luma lines */
|
|
yuv_src[1] += stride >> 1; /* Skip down one chroma line */
|
|
yuv_src[2] += stride >> 1;
|
|
y += 2;
|
|
}
|
|
while (--height > 0);
|
|
}
|
|
|
|
lcd_busy = false;
|
|
}
|
|
|
|
void lcd_init_device()
|
|
{
|
|
as3525_dbop_init();
|
|
|
|
GPIOA_DIR |= (1<<7|1<<5|1<<4|1<<3);
|
|
GPIOA_PIN(5) = 0;
|
|
GPIOA_PIN(3) = (1<<3);
|
|
GPIOA_PIN(4) = 0;
|
|
GPIOA_PIN(7) = 0;
|
|
GPIOA_PIN(5) = (1<<5);
|
|
|
|
_display_on();
|
|
}
|
|
|
|
/* Update the display.
|
|
This must be called after all other LCD functions that change the display. */
|
|
void lcd_update(void)
|
|
{
|
|
if (!display_on)
|
|
return;
|
|
|
|
lcd_write_reg(R_ENTRY_MODE, R_ENTRY_MODE_HORZ);
|
|
|
|
lcd_busy = true;
|
|
lcd_window_x(0, LCD_WIDTH - 1);
|
|
lcd_window_y(0, LCD_HEIGHT - 1);
|
|
|
|
/* Start write to GRAM */
|
|
lcd_write_cmd(R_WRITE_DATA_2_GRAM);
|
|
|
|
/* Write data */
|
|
lcd_write_data((unsigned short *)lcd_framebuffer, LCD_WIDTH*LCD_HEIGHT);
|
|
lcd_busy = false;
|
|
}
|
|
|
|
/* Update a fraction of the display. */
|
|
void lcd_update_rect(int x, int y, int width, int height)
|
|
{
|
|
int xmax, ymax;
|
|
const unsigned short *ptr;
|
|
|
|
if (!display_on)
|
|
return;
|
|
|
|
xmax = x + width;
|
|
if (xmax >= LCD_WIDTH)
|
|
xmax = LCD_WIDTH - 1; /* Clip right */
|
|
if (x < 0)
|
|
x = 0; /* Clip left */
|
|
if (x >= xmax)
|
|
return; /* nothing left to do */
|
|
|
|
width = xmax - x + 1; /* Fix width */
|
|
|
|
ymax = y + height;
|
|
if (ymax >= LCD_HEIGHT)
|
|
ymax = LCD_HEIGHT - 1; /* Clip bottom */
|
|
if (y < 0)
|
|
y = 0; /* Clip top */
|
|
if (y >= ymax)
|
|
return; /* nothing left to do */
|
|
|
|
lcd_write_reg(R_ENTRY_MODE, R_ENTRY_MODE_HORZ);
|
|
|
|
lcd_busy = true;
|
|
lcd_window_x(x, xmax);
|
|
lcd_window_y(y, ymax);
|
|
|
|
/* Start write to GRAM */
|
|
lcd_write_cmd(R_WRITE_DATA_2_GRAM);
|
|
|
|
ptr = (unsigned short *)&lcd_framebuffer[y][x];
|
|
|
|
do
|
|
{
|
|
lcd_write_data(ptr, width);
|
|
ptr += LCD_WIDTH;
|
|
}
|
|
while (++y <= ymax);
|
|
lcd_busy = false;
|
|
}
|
|
|
|
/* writes one read pixel outside the visible area, needed for correct dbop reads */
|
|
bool lcd_button_support(void)
|
|
{
|
|
fb_data data = 0xf<<12;
|
|
if (lcd_busy)
|
|
return false;
|
|
lcd_write_reg(R_ENTRY_MODE, R_ENTRY_MODE_HORZ);
|
|
/* Set start position and window */
|
|
|
|
lcd_window_x(-1, 0);
|
|
lcd_window_y(-1, 0);
|
|
lcd_write_cmd(R_WRITE_DATA_2_GRAM);
|
|
|
|
lcd_write_data(&data, 1);
|
|
|
|
return true;
|
|
}
|