fernly/cmd-lcd.c
bunnie 8f13d99dd4 LCD turns on and draws.
Some basic progress on the LCD, such that the interface can
turn-on and draw.

The implementation uses a slow sequential-write method to update
the display; the vestiges of an auto-copy system are also in
place but not yet debugged.

The system seems to support two methods for issuing commands and
data to the LCD. One is a simple write-to-commit method. The
other is a more sophisticated command list that's set up, and
then dispatched. The write-to-commit kind of works now, although
it's left to be seen if we aren't running too far ahead of
commands or missing some wait states.

To get the demo to work, run the following commands in fernly:

bl 5  (turns on backlight)
lcd su   (sets up GPIOs and timing)
lcd init (initializes the LCD-side controller)
lcd tp1  (blasts a small test pattern to the top of display)

You'll note that the VCOM setting looks to be off (alternate
light/dark lines).

Next steps include:

1) verify that commands are being issued correctly to the LCD
(no missing commands due to writes happening faster than
the interface can keep up)

2) cross-check with LCD datasheet for the initialization
of the controller -- there are some undocumented commands
I left out in this implementation, maybe they need to go in
there; I also left out the gamma table.

3) get the autocopy method to work. I think, from what I can
tell, the autocopy will take a region of interest from memory,
composite it with up to four other regions with alpha blending,
and then auto-copy this to the LCD. You can insert commands
into the auto-copy stream as necessary, but I believe this,
combined with the TE (tearing effect) sync bit, will allow
you to just write to a region of memory as a frame buffer
and have it show up on the display without any CPU intervention.

Could be wrong tho....
2014-09-17 02:44:34 +08:00

293 lines
9.7 KiB
C

#include <string.h>
#include "bionic.h"
#include "memio.h"
#include "printf.h"
#include "serial.h"
#include "fernvale-lcd.h"
#include "fernvale-gpio.h"
#include "fernvale-clockgate.h"
// local implementation of strncmp() because we don't have it as a lib in fernly
static int my_strncmp(const char* s1, const char* s2, size_t n)
{
while(n--) {
if( *s2 == '\0' ) // abort checking if we reach the end of s2
return 0;
if(*s1++!=*s2++)
return *(unsigned char*)(s1 - 1) - *(unsigned char*)(s2 - 1);
}
return 0;
}
static void usleep(uint32_t usecs) {
uint32_t i, j;
for (i = 0; i < usecs; i++) {
for (j = 0; j < 73; j++) {
asm("nop");
}
}
}
static void msleep(uint32_t msecs) {
uint32_t i, j;
for (i = 0; i < msecs; i++) {
for (j = 0; j < 73000; j++) {
asm("nop");
}
}
}
#define USE_DMA 0
#if USE_DMA // needs debuggin'
void flush_dma( uint8_t q ) {
while(*((volatile uint16_t *) LCD_STATUS_REG) & LCD_STATUS_BUSY_BIT)
;
*((volatile uint16_t *) LCD_INT_ENA_REG) &= ~LCD_INT_ENA_TRIG_BIT;
*((volatile uint32_t *) LCD_MEMMAP_CTRL_REG) &= 0x0FFFFFFF; // disable all layers
*((volatile uint32_t *) LCD_MEMMAP_CTRL_REG) &= 0xFFFFE0FF; // clear command count
*((volatile uint32_t *) LCD_MEMMAP_CTRL_REG) |= (q << 8); // enter command count
*((volatile uint32_t *) LCD_MEMMAP_CTRL_REG) |= 0x8000; // enable commands to be sent first
*((volatile uint32_t *)LCD_MEMMAP_SIZE_REG) = 0x0;
*((volatile uint16_t *)LCD_RUN_REG) = 0;
*((volatile uint16_t *)LCD_RUN_REG) = LCD_RUN_BIT;
usleep(1);
while(*((volatile uint16_t *) LCD_STATUS_REG) & LCD_STATUS_BUSY_BIT)
;
*((volatile uint32_t *)LCD_MEMMAP_SIZE_REG) = 0x014000F0;
}
void lcd_cmd( uint16_t cmd ) {
*((volatile uint32_t *)LCD_CMD_LIST_ADDR) = 0x800000 | cmd;
flush_dma(1);
}
void lcd_dat( uint16_t dat ) {
*((volatile uint32_t *)LCD_CMD_LIST_ADDR) = dat;
flush_dma(1);
}
// note these don't flush the DMA, you have to do this yourself explicitly later on
void lcd_cmd_slot( uint16_t cmd, uint8_t slot ) { // slot is the order to execute
*((volatile uint32_t *) (LCD_CMD_LIST_ADDR + (slot << 2))) = 0x800000 | cmd;
}
void lcd_dat_slot( uint16_t dat, uint8_t slot ) { // slot is the order to execute
*((volatile uint32_t *) (LCD_CMD_LIST_ADDR + (slot << 2))) = dat;
}
#else
#define lcd_cmd(_cmd_) *((volatile uint16_t *)LCD_PAR0_CMD_PORT_REG) = (uint16_t) _cmd_
#define lcd_dat(_dat_) *((volatile uint16_t *)LCD_PAR0_DAT_PORT_REG) = (uint16_t) _dat_
#endif
void setup_lcd_gpio() {
// LPCE0, LPTE0, LPRSTB
*((volatile uint32_t *) GPIO_CTRL_MODE5) &= ~( GPIO_CTRL_MODE5_IO40_MASK |
GPIO_CTRL_MODE5_IO46_MASK |
GPIO_CTRL_MODE5_IO45_MASK );
*((volatile uint32_t *) GPIO_CTRL_MODE5) |= ( GPIO_CTRL_MODE5_IO40_LPCE0B |
GPIO_CTRL_MODE5_IO45_LPTE0 |
GPIO_CTRL_MODE5_IO46_LPRSTB );
// NLD0-4, LWRB, LRDB, LPA0
*((volatile uint32_t *) GPIO_CTRL_MODE4) &= ~( GPIO_CTRL_MODE4_IO32_MASK |
GPIO_CTRL_MODE4_IO33_MASK |
GPIO_CTRL_MODE4_IO34_MASK |
GPIO_CTRL_MODE4_IO35_MASK |
GPIO_CTRL_MODE4_IO36_MASK |
GPIO_CTRL_MODE4_IO37_MASK |
GPIO_CTRL_MODE4_IO38_MASK |
GPIO_CTRL_MODE4_IO39_MASK );
*((volatile uint32_t *) GPIO_CTRL_MODE4) |= ( GPIO_CTRL_MODE4_IO32_NLD4 |
GPIO_CTRL_MODE4_IO33_NLD3 |
GPIO_CTRL_MODE4_IO34_NLD2 |
GPIO_CTRL_MODE4_IO35_NLD1 |
GPIO_CTRL_MODE4_IO36_NLD0 |
GPIO_CTRL_MODE4_IO37_LWRB |
GPIO_CTRL_MODE4_IO38_LRDB |
GPIO_CTRL_MODE4_IO39_LPA0 );
// NLD5-8
*((volatile uint32_t *) GPIO_CTRL_MODE3) &= ~( GPIO_CTRL_MODE3_IO28_MASK |
GPIO_CTRL_MODE3_IO29_MASK |
GPIO_CTRL_MODE3_IO30_MASK |
GPIO_CTRL_MODE3_IO31_MASK );
*((volatile uint32_t *) GPIO_CTRL_MODE3) |= ( GPIO_CTRL_MODE3_IO28_NLD8 |
GPIO_CTRL_MODE3_IO29_NLD7 |
GPIO_CTRL_MODE3_IO30_NLD6 |
GPIO_CTRL_MODE3_IO31_NLD5 );
}
int cmd_lcd(int argc, char **argv)
{
uint32_t genarg = 0;
char *subcmd;
int i;
if (argc < 1) {
printf("Usage: lcd [subcmd] [arg0]\n");
return -1;
}
subcmd = argv[0];
if (argc == 2 ) {
genarg = _strtoul(argv[1], NULL, 0);
}
printf( "command got: %s\n", subcmd );
if( my_strncmp( subcmd, "su", 8 ) == 0 ) {
setup_lcd_gpio();
*((volatile uint32_t *) CLKGATE_SYS_CTL0_CLR) = CLKGATE_CTL0_LCD; // power up the LCD block
msleep(1);
// execute setup command
// we're on CS0
// our internal bus period is 166 MHz, or 6.25ns
// write cycle = 66ns = 11 cycles - 1 = 10
// write c22write su (tcs) = 15ns = 3 cycles
// write ce2write hold (tdht) = 10ns = 2 cycles - 1 = 1
// read latency = 450 ns = 72 cycles, crop at 63 cycles
// read ce2read su (trdl - trcs) = 45-45 = 0 ns = 0 cycles
// read th = 90ns = 15 cycles (not quite clear, but best guess)
*((volatile uint32_t *)LCD_PAR0_CFG_REG) =
(10 << LCD_PAR_CFG_WR_WAIT_CYC_BIT) |
(3 << LCD_PAR_CFG_WR_TSU_BIT) |
(1 << LCD_PAR_CFG_WR_TH_BIT) |
(63 << LCD_PAR_CFG_RD_LATENCY_CYC_BIT ) | // this might need to be shorter??
(0 << LCD_PAR_CFG_RD_TSU_BIT) |
(15 << LCD_PAR_CFG_RD_TH_BIT);
// 9 bit width, tchw is 0 for this chipset (back2back writes allowed)
*((volatile uint32_t *)LCD_PAR_DATA_WIDTH_REG) =
(0 << LCD_PAR_W2W_WAIT0_BIT) |
(LCD_PAR_BUS_WIDTH_9BIT << LCD_PAR_BUS_WIDTH0_BIT);
*((volatile uint32_t *)LCD_AUTOCOPY_CTRL_REG) = 0; // AUTOCOPY off initially
} else if( my_strncmp( subcmd, "auto", 8 ) == 0 ) {
*((volatile uint32_t *)LCD_LAYER0_CTRL_REG) = 0;
*((volatile uint32_t *)LCD_LAYER0_OFFSET_REG) = 0;
*((volatile uint32_t *)LCD_LAYER0_BUFF_ADDR_REG) = 0;
*((volatile uint32_t *)LCD_LAYER0_SIZE_REG) = 0x014000f0;
*((volatile uint32_t *)LCD_LAYER0_MEM_OFFSET_REG) = 0x0;
*((volatile uint32_t *)LCD_LAYER0_MEM_PITCH_REG) = 0x1e0;
*((volatile uint32_t *)LCD_AUTOCOPY_OFFSET_REG) = 0;
*((volatile uint16_t *)LCD_AUTOCOPY_CMD_ADDR_REG) = 0x4000;
*((volatile uint16_t *)LCD_AUTOCOPY_DATA_ADDR_REG) = 0x4100;
*((volatile uint32_t *)LCD_AUTOCOPY_SIZE_REG) = 0x014000F0;
*((volatile uint32_t *)LCD_AUTOCOPY_BG_COLOR_REG) = 0x80008000;
*((volatile uint32_t *)LCD_AUTOCOPY_CTRL_REG) = 0x85020094;
} else if( my_strncmp( subcmd, "dump", 8 ) == 0 ) {
// dump registers
printf( "LCD_PAR0_CMD_PORT_REG: %04x\n", *((volatile uint16_t *) LCD_PAR0_CMD_PORT_REG) );
printf( "LCD_PAR0_DAT_PORT_REG: %04x\n", *((volatile uint16_t *) LCD_PAR0_DAT_PORT_REG) );
printf( "LCD_PAR1_CMD_PORT_REG: %04x\n", *((volatile uint16_t *) LCD_PAR1_CMD_PORT_REG) );
printf( "LCD_PAR1_DAT_PORT_REG: %04x\n", *((volatile uint16_t *) LCD_PAR1_DAT_PORT_REG) );
printf( "LCD_PAR0_CFG_REG: %08x\n", *((volatile uint32_t *) LCD_PAR0_CFG_REG) );
printf( "LCD_PAR1_CFG_REG: %08x\n", *((volatile uint32_t *) LCD_PAR1_CFG_REG) );
printf( "LCD_STATUS_REG: %04x\n", *((volatile uint16_t *) LCD_STATUS_REG) );
printf( "LCD_INT_ENA_REG: %04x\n", *((volatile uint16_t *) LCD_INT_ENA_REG) );
printf( "LCD_INT_STAT_REG: %04x\n", *((volatile uint16_t *) LCD_INT_STAT_REG) );
printf( "LCD_RUN_REG: %04x\n", *((volatile uint16_t *) LCD_RUN_REG) );
printf( "LCD_RESET_REG: %04x\n", *((volatile uint16_t *) LCD_RESET_REG) );
printf( "LCD_PAR_DATA_WIDTH_REG: %08x\n", *((volatile uint32_t *) LCD_PAR_DATA_WIDTH_REG) );
printf( "LCD_TEARING_CON_REG: %08x\n", *((volatile uint32_t *) LCD_TEARING_CON_REG) );
printf( "LCD_AUTOCOPY_CTRL_REG: %08x\n", *((volatile uint32_t *) LCD_AUTOCOPY_CTRL_REG) );
} else if( my_strncmp( subcmd, "run", 8 ) == 0 ) {
// cause the interface to run
*((volatile uint16_t *)LCD_RUN_REG) = 0;
*((volatile uint16_t *)LCD_RUN_REG) = LCD_RUN_BIT;
} else if( my_strncmp( subcmd, "stop", 8 ) == 0 ) {
// cause the interface to stop
*((volatile uint16_t *)LCD_RUN_REG) = 0;
} else if( my_strncmp( subcmd, "init", 8 ) == 0 ) {
*((volatile uint16_t *)LCD_RESET_REG) = 1;
usleep(20000);
*((volatile uint16_t *)LCD_RESET_REG) = 0; // turn on reset
msleep(20);
*((volatile uint16_t *)LCD_RESET_REG) = 1; // turn off reset
msleep(150);
lcd_cmd(0x11); //Exit Sleep
msleep(50); // Delay 50ms
lcd_cmd(0xC0); //Power control
lcd_dat(0x26);
lcd_cmd(0xC1); //Power control
lcd_dat(0x11); //SAP[2:0];BT[3:0]
lcd_cmd(0xC5); //VCM control
lcd_dat(0x35);
lcd_dat(0x3E);
lcd_cmd(0xc7);
lcd_dat(0xbe);
lcd_cmd(0x36); // Memory Access Control
lcd_dat(0x48);
lcd_cmd(0x3a); // pixel format set
lcd_dat(0x55); // 16bpp
lcd_cmd(0xB1); // Frame Rate Control
lcd_dat(0x00);
lcd_dat(0x1b);
//--------------ddram ---------------------
lcd_cmd(0x2a); // column set
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0xEF);
lcd_cmd(0x2b); // page address set
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0x01);
lcd_dat(0x3F);
lcd_cmd(0x34); // tearing effect off
//lcd_cmd(0x35); // tearing effect on
//lcd_cmd(0xb4); // display inversion
//lcd_dat(0x00,0x00);
lcd_cmd(0xb7); //entry mode set
lcd_dat(0x07);
//-----------------display---------------------
lcd_cmd(0xb6); // display function control
lcd_dat(0x0a);
lcd_dat(0x82);
lcd_dat(0x27);
lcd_dat(0x00);
lcd_cmd(0x11); //sleep out
msleep(100);
lcd_cmd(0x29); // display on
msleep(100);
lcd_cmd(0x2c); //memory write
} else if( my_strncmp( subcmd, "tp1", 8 ) == 0 ) {
lcd_cmd(0x2a); // column set
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0xEF);
lcd_cmd(0x2b); // page address set
lcd_dat(0x00);
lcd_dat(0x00);
lcd_dat(0x01);
lcd_dat(0x3F);
lcd_cmd(0x2c); //memory write
for( i = 0; i < 16384; i++ ) {
lcd_dat((uint16_t) i);
}
} else {
printf( "lcd commands: su (setup timing), dump (dump registers), run, stop, init, tp1\n" );
}
return 0;
}