mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-07-10 13:29:52 -04:00
Enable HAVE_LCD_FLIP for the Clip Zip and implement lcd_set_flip() in the LCD driver, making the Display -> Flip Display setting work. This lets the player be used upside down, e.g. clipped to clothing with the control buttons pointing up and screen on the bottom. Defining HAVE_LCD_FLIP also activates the existing button remap in button_flip() (firmware/drivers/button.c) for this target: while the display is flipped, LEFT/RIGHT, UP/DOWN and the volume keys are all swapped to match the new orientation, so the whole device is usable upside down, not just readable. The flip is done in hardware by reversing the controller's GRAM write direction and mirroring the write window in lcd_setup_rect, so partial updates keep working and there is no per-frame cost. Both panel variants are handled: the type 0 WiseChip/SEPS114A via MEMORY_WRITE/READ (1Dh, 0x02), and the type 1 Visionox/LD7134 via the Graphic RAM Writing Direction register (05h, 0x03). The direction register is written in lcd_enable(), so it is set while the panel is powered and is re-applied after display standby; lcd_set_flip() cycles the panel off and on so a change to the setting takes effect immediately. For the simulator, which has no real LCD controller, lcd_set_flip() is implemented in the SDL LCD driver (lcd-bitmap.c) as a software mirror of the framebuffer, so the flip is visible in theme previews; the generic uisimulator stub is guarded out when HAVE_LCD_FLIP is defined. Tested on real type 1 / LD7134 hardware in both orientations: display content, button remapping and album art are all correct, and test_fps shows partial updates run at full speed when flipped (1/4 frame 325 fps, matching the non-flipped rate). The type 0 / SEPS114A path uses the same approach; the 0x02 direction value was confirmed to flip a type 0 panel by William Wilgus during review. Change-Id: I99ef13949102b344826e72d1d90c71e2271448a6
473 lines
13 KiB
C
473 lines
13 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2011 Bertrik Sikken
|
|
*
|
|
* 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 "system.h"
|
|
#include "kernel.h"
|
|
|
|
#include "lcd.h"
|
|
#include "lcd-target.h"
|
|
|
|
/* the detected lcd type (0 or 1) */
|
|
static int lcd_type;
|
|
|
|
/* set by lcd_set_flip(): when true the panel is rotated 180 degrees via the
|
|
controller's GRAM write direction (see lcd_set_flip and lcd_setup_rect) */
|
|
static bool lcd_flipped = false;
|
|
|
|
#ifdef HAVE_LCD_ENABLE
|
|
/* whether the lcd is currently enabled or not */
|
|
static bool lcd_enabled;
|
|
#endif
|
|
|
|
static void ssp_set_prescaler(unsigned int prescaler)
|
|
{
|
|
int oldlevel = disable_interrupt_save(IRQ_FIQ_STATUS);
|
|
/* must be on to write regs */
|
|
bool ssp_enabled = bitset32(&CGU_PERI, CGU_SSP_CLOCK_ENABLE) &
|
|
CGU_SSP_CLOCK_ENABLE;
|
|
SSP_CPSR = prescaler;
|
|
|
|
if (!ssp_enabled) /* put it back how we found it */
|
|
bitclr32(&CGU_PERI, CGU_SSP_CLOCK_ENABLE);
|
|
|
|
restore_irq(oldlevel);
|
|
}
|
|
|
|
/* initialises the host lcd hardware, returns the lcd type */
|
|
static int lcd_hw_init(void)
|
|
{
|
|
/* configure SSP */
|
|
bitset32(&CGU_PERI, CGU_SSP_CLOCK_ENABLE);
|
|
ssp_set_prescaler(AS3525_SSP_PRESCALER); /* OF = 0x8 */
|
|
SSP_CR0 = (0 << 8) | /* SCR, serial clock rate divider = 1 */
|
|
(1 << 7) | /* SPH, phase = 1 */
|
|
(1 << 6) | /* SPO, polarity = 1 */
|
|
(0 << 4) | /* FRF, frame format = motorola SPI */
|
|
(7 << 0); /* DSS, data size select = 8 bits */
|
|
SSP_CR1 = (1 << 3) | /* SOD, slave output disable = 1 */
|
|
(0 << 2) | /* MS, master/slave = master */
|
|
(1 << 1) | /* SSE, synchronous serial port enabled = true */
|
|
(0 << 0); /* LBM, loopback mode = normal */
|
|
SSP_IMSC &= ~0xF; /* disable interrupts */
|
|
SSP_DMACR &= ~0x3; /* disable DMA */
|
|
|
|
/* GPIO A3 is ??? but needs to be set */
|
|
GPIOA_DIR |= (1 << 3);
|
|
GPIOA_PIN(3) = (1 << 3);
|
|
|
|
/* configure GPIO B2 (lcd D/C#) as output */
|
|
GPIOB_DIR |= (1<<2);
|
|
|
|
/* configure GPIO B3 (lcd type detect) as input */
|
|
GPIOB_DIR &= ~(1<<3);
|
|
|
|
/* configure GPIO A5 (lcd reset#) as output and perform lcd reset */
|
|
GPIOA_DIR |= (1 << 5);
|
|
GPIOA_PIN(5) = 0;
|
|
sleep(HZ * 50/1000);
|
|
GPIOA_PIN(5) = (1 << 5);
|
|
|
|
/* detect lcd type on GPIO B3 */
|
|
return GPIOB_PIN(3) ? 1 : 0;
|
|
}
|
|
|
|
/* writes a command byte to the LCD */
|
|
static void lcd_write_cmd(uint8_t byte)
|
|
{
|
|
/* wait until not busy */
|
|
while (SSP_SR & (1<<4));
|
|
|
|
/* LCD command mode */
|
|
GPIOB_PIN(2) = 0;
|
|
|
|
/* write data */
|
|
SSP_DATA = byte;
|
|
|
|
/* wait until not busy */
|
|
while (SSP_SR & (1<<4));
|
|
|
|
/* LCD data mode */
|
|
GPIOB_PIN(2) = (1 << 2);
|
|
}
|
|
|
|
/* writes a data byte to the LCD */
|
|
static void lcd_write_dat(uint8_t data)
|
|
{
|
|
/* wait while transmit FIFO */
|
|
while (!(SSP_SR & (1<<1)));
|
|
|
|
/* write data */
|
|
SSP_DATA = data;
|
|
}
|
|
|
|
/* writes 2 data bytes to the LCD */
|
|
static void lcd_write_dat_word(uint8_t data1, uint8_t data2)
|
|
{
|
|
lcd_write_dat(data1);
|
|
lcd_write_dat(data2);
|
|
}
|
|
|
|
/* writes both a command and data value to the lcd */
|
|
static void lcd_write(uint8_t cmd, uint8_t data)
|
|
{
|
|
lcd_write_cmd(cmd);
|
|
lcd_write_dat(data);
|
|
}
|
|
|
|
/* Initialises lcd type 0
|
|
* This appears to be a WiseChip OLED display controlled by a SEPS114A.
|
|
*/
|
|
static void lcd_init_type0(void)
|
|
{
|
|
lcd_write(0x01, 0x00); /* SOFT_RESET */
|
|
lcd_write(0x14, 0x01); /* STANDBY_ON_OFF */
|
|
sleep(1); /* actually only 5 ms needed */
|
|
|
|
lcd_write(0x14, 0x00); /* STANDBY_ON_OFF */
|
|
sleep(1); /* actually only 5 ms needed */
|
|
|
|
lcd_write(0x0F, 0x41); /* ANALOG_CONTROL */
|
|
lcd_write(0xEA, 0x0A); /* ? */
|
|
lcd_write(0xEB, 0x42); /* ? */
|
|
lcd_write(0x18, 0x08); /* DISCHARGE_TIME */
|
|
lcd_write(0x1A, 0x0B); /* OSC_ADJUST */
|
|
lcd_write(0x48, 0x03); /* ROW_OVERLAP */
|
|
lcd_write(0x30, 0x00); /* DISPLAY_X1 */
|
|
lcd_write(0x31, 0x5F); /* DISPLAY_X2 */
|
|
lcd_write(0x32, 0x00); /* DISPLAY_Y1 */
|
|
lcd_write(0x33, 0x5F); /* DISPLAY_Y2 */
|
|
lcd_write(0xE0, 0x10); /* RGB_IF */
|
|
lcd_write(0xE1, 0x00); /* RGB_POL */
|
|
lcd_write(0xE5, 0x80); /* DISPLAY_MODE_CONTROL */
|
|
lcd_write(0x0D, 0x00); /* CPU_IF */
|
|
lcd_write(0x1D, 0x01); /* MEMORY_WRITE_READ */
|
|
lcd_write(0x09, 0x00); /* ROW_SCAN_DIRECTION */
|
|
lcd_write(0x13, 0x00); /* ROW_SCAN_MODE */
|
|
lcd_write(0x16, 0x05); /* PEAK_PULSE_DELAY */
|
|
lcd_write(0x3A, 0x03); /* PEAK_PULSE_WIDTH_R */
|
|
lcd_write(0x3B, 0x03); /* PEAK_PULSE_WIDTH_G */
|
|
lcd_write(0x3C, 0x03); /* PEAK_PULSE_WIDTH_B */
|
|
lcd_write(0x3D, 0x45); /* PRECHARGE_CURRENT_R */
|
|
lcd_write(0x3E, 0x45); /* PRECHARGE_CURRENT_G */
|
|
lcd_write(0x3F, 0x45); /* PRECHARGE_CURRENT_B */
|
|
lcd_write(0x40, 0x62); /* COLUMN_CURRENT_R */
|
|
lcd_write(0x41, 0x3D); /* COLUMN_CURRENT_G */
|
|
lcd_write(0x42, 0x46); /* COLUMN_CURRENT_B */
|
|
}
|
|
|
|
/* writes a table entry (for type 1 LCDs) */
|
|
static void lcd_write_nibbles(uint8_t val)
|
|
{
|
|
lcd_write_dat_word((val >> 4) & 0x0F, (val >> 0) & 0x0F);
|
|
}
|
|
|
|
/* Initialises lcd type 1
|
|
* This appears to be a Visionox OLED display, with a LDT LD7134 controller
|
|
*/
|
|
static void lcd_init_type1(void)
|
|
{
|
|
static const uint8_t curve[128] = {
|
|
/* 5-bit curve */
|
|
0, 5, 10, 15, 20, 25, 30, 35, 39, 43, 47, 51, 55, 59, 63, 67,
|
|
71, 75, 79, 83, 87, 91, 95, 99, 103, 105, 109, 113, 117, 121, 123, 127,
|
|
/* 6-bit curve */
|
|
0, 2, 4, 6, 8, 10, 12, 16, 18, 24, 26, 28, 30, 32, 34, 36,
|
|
38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68,
|
|
70, 72, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102,
|
|
104, 106, 108, 110, 112, 114, 116, 118, 120, 121, 122, 123, 124, 125, 126, 127,
|
|
/* 5-bit curve */
|
|
0, 5, 10, 15, 20, 25, 30, 35, 39, 43, 47, 51, 55, 59, 63, 67,
|
|
71, 75, 79, 83, 87, 91, 93, 97, 101, 105, 109, 113, 117, 121, 124, 127
|
|
};
|
|
int i;
|
|
|
|
lcd_write(0x02, 0x00);
|
|
|
|
lcd_write_cmd(0x01);
|
|
|
|
lcd_write(0x03, 0x00);
|
|
|
|
lcd_write(0x04, 0x03);
|
|
|
|
lcd_write(0x05, 0x00); /* 0x08 results in BGR colour */
|
|
|
|
lcd_write(0x06, 0x00);
|
|
|
|
lcd_write(0x07, 0x00);
|
|
lcd_write_dat_word(0x00, 0x04);
|
|
lcd_write_dat_word(0x1F, 0x00);
|
|
lcd_write_dat_word(0x00, 0x05);
|
|
lcd_write_dat(0x0F);
|
|
|
|
lcd_write(0x08, 0x01);
|
|
|
|
lcd_write(0x09, 0x07);
|
|
|
|
lcd_write_cmd(0x0A);
|
|
lcd_write_nibbles(0);
|
|
lcd_write_nibbles(LCD_WIDTH - 1);
|
|
lcd_write_nibbles(0);
|
|
lcd_write_nibbles(LCD_HEIGHT - 1);
|
|
|
|
lcd_write(0x0B, 0x00);
|
|
lcd_write_dat_word(0x00, 0x00);
|
|
lcd_write_dat(0x00);
|
|
|
|
lcd_write_cmd(0x0E);
|
|
lcd_write_nibbles(0x42);
|
|
lcd_write_nibbles(0x25);
|
|
lcd_write_nibbles(0x3F);
|
|
|
|
lcd_write(0x0F, 0x0A);
|
|
lcd_write_dat_word(0x0A, 0x0A);
|
|
|
|
lcd_write(0x1C, 0x08);
|
|
|
|
lcd_write(0x1D, 0x00);
|
|
lcd_write_dat_word(0x00, 0x00);
|
|
|
|
lcd_write(0x1E, 0x05);
|
|
|
|
lcd_write(0x1F, 0x00);
|
|
|
|
lcd_write(0x30, 0x10);
|
|
|
|
lcd_write_cmd(0x3A);
|
|
for (i = 0; i < 128; i++) {
|
|
lcd_write_nibbles(curve[i]);
|
|
}
|
|
|
|
lcd_write(0x3C, 0x00);
|
|
|
|
lcd_write(0x3D, 0x00);
|
|
}
|
|
|
|
#ifdef HAVE_LCD_ENABLE
|
|
/* enables/disables the lcd */
|
|
void lcd_enable(bool on)
|
|
{
|
|
if (on == lcd_enabled) {
|
|
return;
|
|
}
|
|
|
|
if (lcd_type == 0) {
|
|
if (on) {
|
|
lcd_write(0x14, 0x00); /* STANDBY_ON_OFF */
|
|
lcd_write(0x02, 0x01); /* DISP_ON_OFF */
|
|
lcd_write(0xD2, 0x04); /* SCREEN_SAVER_MODE */
|
|
lcd_write(0xD0, 0x80); /* SCREEN_SAVER_CONTROL */
|
|
sleep(HZ * 100/1000);
|
|
|
|
lcd_write(0xD0, 0x00); /* SCREEN_SAVER_CONTROL */
|
|
|
|
/* apply 180 degree flip via memory write direction (1Dh):
|
|
MDIR1|MDIR0 = decrement both; normal is horizontal-decrement */
|
|
lcd_write(0x1D, lcd_flipped ? 0x02 : 0x01);
|
|
}
|
|
else {
|
|
lcd_write(0xD2, 0x05);
|
|
lcd_write(0xD0, 0x80);
|
|
sleep(HZ * 100/1000);
|
|
|
|
lcd_write(0x02, 0x00);
|
|
lcd_write(0xD0, 0x00);
|
|
lcd_write(0x14, 0x01);
|
|
}
|
|
}
|
|
else {
|
|
if (on) {
|
|
lcd_write(0x03, 0x00);
|
|
|
|
lcd_write(0x02, 0x01);
|
|
|
|
/* apply 180 degree flip via graphic RAM writing direction (05h):
|
|
D[2:0] = 011 starts from XE,YE (both axes reversed) */
|
|
lcd_write(0x05, lcd_flipped ? 0x03 : 0x00);
|
|
}
|
|
else {
|
|
lcd_write(0x02, 0x00);
|
|
|
|
lcd_write(0x03, 0x01);
|
|
}
|
|
}
|
|
|
|
lcd_enabled = on;
|
|
}
|
|
|
|
/* returns true if the lcd is enabled */
|
|
bool lcd_active(void)
|
|
{
|
|
return lcd_enabled;
|
|
}
|
|
#endif /* HAVE_LCD_ENABLE */
|
|
|
|
/* initialises the lcd */
|
|
void lcd_init_device(void)
|
|
{
|
|
lcd_type = lcd_hw_init();
|
|
if (lcd_type == 0) {
|
|
lcd_init_type0();
|
|
}
|
|
else {
|
|
lcd_init_type1();
|
|
}
|
|
lcd_enable(true);
|
|
}
|
|
|
|
/* sets up the lcd to receive frame buffer data */
|
|
static void lcd_setup_rect(int x, int x_end, int y, int y_end)
|
|
{
|
|
if (lcd_flipped) {
|
|
/* Mirror the window to the opposite corner; the reversed write
|
|
direction set in lcd_enable() fills it back-to-front, drawing the
|
|
framebuffer rotated 180 degrees with no per-pixel work. */
|
|
int fx = LCD_WIDTH - 1 - x_end;
|
|
int fy = LCD_HEIGHT - 1 - y_end;
|
|
x_end = LCD_WIDTH - 1 - x;
|
|
y_end = LCD_HEIGHT - 1 - y;
|
|
x = fx;
|
|
y = fy;
|
|
}
|
|
|
|
if (lcd_type == 0) {
|
|
lcd_write(0x34, x); /* MEM_X1 */
|
|
lcd_write(0x35, x_end); /* MEM_X2 */
|
|
lcd_write(0x36, y); /* MEM_Y1 */
|
|
lcd_write(0x37, y_end); /* MEM_Y2 */
|
|
|
|
lcd_write_cmd(0x08); /* DDRAM_DATA_ACCESS_PORT */
|
|
}
|
|
else {
|
|
lcd_write_cmd(0x0A);
|
|
lcd_write_nibbles(x);
|
|
lcd_write_nibbles(x_end);
|
|
lcd_write_nibbles(y);
|
|
lcd_write_nibbles(y_end);
|
|
|
|
lcd_write_cmd(0x0C);
|
|
}
|
|
}
|
|
|
|
/* sets the brightness of the OLED */
|
|
void oled_brightness(int brightness)
|
|
{
|
|
int r, g, b;
|
|
|
|
if (lcd_type == 0) {
|
|
r = 2 + 16*brightness;
|
|
g = 1 + 10*brightness;
|
|
b = 1 + (23*brightness)/2;
|
|
|
|
lcd_write(0x40, r); /* COLUMN_CURRENT_R */
|
|
lcd_write(0x41, g); /* COLUMN_CURRENT_G */
|
|
lcd_write(0x42, b); /* COLUMN_CURRENT_B */
|
|
}
|
|
else {
|
|
r = 6 + 10*brightness;
|
|
g = 1 + 6*brightness;
|
|
b = 3 + 10*brightness;
|
|
|
|
lcd_write_cmd(0x0E);
|
|
lcd_write_nibbles(r);
|
|
lcd_write_nibbles(g);
|
|
lcd_write_nibbles(b);
|
|
}
|
|
}
|
|
|
|
/* Writes framebuffer data */
|
|
void lcd_write_data(const fb_data *data, int count)
|
|
{
|
|
fb_data pixel;
|
|
|
|
while (count--) {
|
|
pixel = *data++;
|
|
lcd_write_dat_word((pixel >> 8) & 0xFF, (pixel >> 0) & 0xFF);
|
|
}
|
|
}
|
|
|
|
/* Rotate the display 180 degrees (the flip_display setting). Both panel
|
|
controllers do this in hardware by reversing the GRAM write direction (set in
|
|
lcd_enable) so the address counter walks the framebuffer backwards;
|
|
lcd_setup_rect mirrors the write window to match. Cycle the panel off/on here
|
|
so a change (e.g. toggled from the menu) takes effect immediately. */
|
|
void lcd_set_flip(bool yesno)
|
|
{
|
|
if (yesno == lcd_flipped)
|
|
return;
|
|
lcd_flipped = yesno;
|
|
|
|
if (lcd_enabled) {
|
|
lcd_enable(false);
|
|
lcd_enable(true);
|
|
}
|
|
}
|
|
|
|
/* Updates a fraction of the display. */
|
|
void lcd_update_rect(int x, int y, int width, int height)
|
|
{
|
|
int row;
|
|
int x_end = x + width;
|
|
int y_end = y + height;
|
|
|
|
/* check rectangle */
|
|
if ((x >= LCD_WIDTH) || (x_end <= 0) || (y >= LCD_HEIGHT) || (y_end <= 0)) {
|
|
/* rectangle is outside visible display, do nothing */
|
|
return;
|
|
}
|
|
|
|
/* update entire horizontal strip for display type 0 (wisechip) */
|
|
if (lcd_type == 0) {
|
|
x = 0;
|
|
x_end = 96;
|
|
}
|
|
|
|
/* correct rectangle (if necessary) */
|
|
if (x < 0) {
|
|
x = 0;
|
|
}
|
|
if (x_end > LCD_WIDTH) {
|
|
x_end = LCD_WIDTH;
|
|
}
|
|
if (y < 0) {
|
|
y = 0;
|
|
}
|
|
if (y_end > LCD_HEIGHT) {
|
|
y_end = LCD_HEIGHT;
|
|
}
|
|
width = x_end - x;
|
|
|
|
/* setup GRAM write window */
|
|
lcd_setup_rect(x, x_end - 1, y, y_end - 1);
|
|
|
|
void* (*fbaddr)(int x, int y) = FB_CURRENTVP_BUFFER->get_address_fn;
|
|
/* write to GRAM */
|
|
for (row = y; row < y_end; row++) {
|
|
lcd_write_data(fbaddr(x,row), width);
|
|
}
|
|
}
|
|
|
|
/* updates the entire lcd */
|
|
void lcd_update(void)
|
|
{
|
|
lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT);
|
|
}
|
|
|