blob: a8d83d3708c776bd261129298744dce1bd31c7cb [file] [log] [blame]
/*
* Copyright (c) 2011 The Chromium OS Authors.
* See file CREDITS for list of people who contributed to this
* project.
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <lcd.h>
#include <fdt_decode.h>
#include <asm/clocks.h>
#include <asm/arch/clock.h>
#include <asm/arch/gpio.h>
#include <asm/arch/pinmux.h>
#include <asm/arch/power.h>
#include <asm/arch/pwfm.h>
#include <asm/arch/display.h>
#include <asm/system.h>
DECLARE_GLOBAL_DATA_PTR;
unsigned long timer_last;
int lcd_line_length;
int lcd_color_fg;
int lcd_color_bg;
void *lcd_base; /* Start of framebuffer memory */
void *lcd_console_address; /* Start of console buffer */
short console_col;
short console_row;
vidinfo_t panel_info = {
/* Insert a value here so that we don't end up in the BSS */
.vl_col = -1,
};
char lcd_cursor_enabled = 0; /* set initial value to false */
ushort lcd_cursor_width;
ushort lcd_cursor_height;
static void clk_init(void)
{
/* TODO: Put this into the FDT when we have clock support there */
clock_start_periph_pll(PERIPH_ID_3D, CLOCK_ID_MEMORY, CLK_300M);
clock_start_periph_pll(PERIPH_ID_2D, CLOCK_ID_MEMORY, CLK_300M);
clock_start_periph_pll(PERIPH_ID_HOST1X, CLOCK_ID_PERIPH, CLK_144M);
clock_start_periph_pll(PERIPH_ID_DISP1, CLOCK_ID_CGENERAL, CLK_600M);
clock_start_periph_pll(PERIPH_ID_PWM, CLOCK_ID_SFROM32KHZ, CLK_32768);
}
/*
* The PINMUX macro is used per board to setup the pinmux configuration.
*/
#define PINMUX(grp, mux, pupd, tri) \
{PINGRP_##grp, PMUX_FUNC_##mux, PMUX_PULL_##pupd, PMUX_TRI_##tri}
struct pingroup_config pinmux_cros_1[] = {
PINMUX(GPU, PWM, NORMAL, NORMAL),
PINMUX(LD0, DISPA, NORMAL, NORMAL),
PINMUX(LD1, DISPA, NORMAL, NORMAL),
PINMUX(LD10, DISPA, NORMAL, NORMAL),
PINMUX(LD11, DISPA, NORMAL, NORMAL),
PINMUX(LD12, DISPA, NORMAL, NORMAL),
PINMUX(LD13, DISPA, NORMAL, NORMAL),
PINMUX(LD14, DISPA, NORMAL, NORMAL),
PINMUX(LD15, DISPA, NORMAL, NORMAL),
PINMUX(LD16, DISPA, NORMAL, NORMAL),
PINMUX(LD17, DISPA, NORMAL, NORMAL),
PINMUX(LD2, DISPA, NORMAL, NORMAL),
PINMUX(LD3, DISPA, NORMAL, NORMAL),
PINMUX(LD4, DISPA, NORMAL, NORMAL),
PINMUX(LD5, DISPA, NORMAL, NORMAL),
PINMUX(LD6, DISPA, NORMAL, NORMAL),
PINMUX(LD7, DISPA, NORMAL, NORMAL),
PINMUX(LD8, DISPA, NORMAL, NORMAL),
PINMUX(LD9, DISPA, NORMAL, NORMAL),
PINMUX(LDI, DISPA, NORMAL, NORMAL),
PINMUX(LHP0, DISPA, NORMAL, NORMAL),
PINMUX(LHP1, DISPA, NORMAL, NORMAL),
PINMUX(LHP2, DISPA, NORMAL, NORMAL),
PINMUX(LHS, DISPA, NORMAL, NORMAL),
PINMUX(LM0, RSVD4, NORMAL, NORMAL),
PINMUX(LPP, DISPA, NORMAL, NORMAL),
PINMUX(LPW0, RSVD4, NORMAL, NORMAL),
PINMUX(LPW1, RSVD4, NORMAL, TRISTATE),
PINMUX(LPW2, RSVD4, NORMAL, NORMAL),
PINMUX(LSC0, DISPA, NORMAL, NORMAL),
PINMUX(LSPI, DISPA, NORMAL, NORMAL),
PINMUX(LVP1, DISPA, NORMAL, NORMAL),
PINMUX(LVS, DISPA, NORMAL, NORMAL),
PINMUX(SLXD, SPDIF, NORMAL, NORMAL),
};
/* Initialize the Tegra LCD pinmuxs */
static void init_lcd_pinmux(struct fdt_lcd *config)
{
/* TODO: put pinmux into the FDT */
pinmux_config_table(pinmux_cros_1, ARRAY_SIZE(pinmux_cros_1));
fdt_setup_gpio(&config->panel_vdd);
fdt_setup_gpio(&config->lvds_shutdown);
fdt_setup_gpio(&config->backlight_vdd);
fdt_setup_gpio(&config->backlight_en);
gpio_set_value(config->panel_vdd.gpio, 1);
udelay(config->panel_timings[0] * 1000);
gpio_set_value(config->lvds_shutdown.gpio, 1);
timer_last = timer_get_us();
}
/* Initialize the Tegra LCD panel and controller */
void init_lcd(struct fdt_lcd *config)
{
clk_init();
power_enable_partition(POWERP_3D);
tegra2_display_register(config);
}
static void init_lcd_pwm(struct fdt_lcd *config)
{
long pre_delay;
/*
* Compare the current timer valuse with the timer_last to
* figure out the pre_delay which is the passed time from
* the end of init_lcd_pinmux to now.
* If the required delay(timings[1]) is bigger than pre_delay,
* we have to do a delay (timings[1] - pre_delay) to make
* sure the required delay(timings[1]) has passed.
*/
pre_delay = timer_get_us() - timer_last;
if ((long)(config->panel_timings[1] * 1000) > pre_delay)
udelay((long)(config->panel_timings[1] * 1000) - pre_delay);
gpio_set_value(config->backlight_vdd.gpio, 1);
udelay(config->panel_timings[2] * 1000);
/* Enable PWM at 15/16 high, divider 1 */
pwfm_setup(config->pwfm, 1, 0xdf, 1);
udelay(config->panel_timings[3] * 1000);
gpio_set_value(config->backlight_en.gpio, 1);
}
void lcd_cursor_size(ushort width, ushort height)
{
lcd_cursor_width = width;
lcd_cursor_height = height;
}
void lcd_toggle_cursor(void)
{
ushort x, y;
uchar *dest;
ushort row;
x = console_col * lcd_cursor_width;
y = console_row * lcd_cursor_height;
dest = (uchar *)(lcd_base + y * lcd_line_length + x * (1 << LCD_BPP) /
8);
for (row = 0; row < lcd_cursor_height; ++row, dest += lcd_line_length) {
ushort *d = (ushort *)dest;
ushort color;
int i;
for (i = 0; i < lcd_cursor_width; ++i) {
color = *d;
color ^= lcd_color_fg;
*d = color;
++d;
}
}
}
void lcd_cursor_on(void)
{
lcd_cursor_enabled = 1;
lcd_toggle_cursor();
}
void lcd_cursor_off(void)
{
lcd_cursor_enabled = 0;
lcd_toggle_cursor();
}
char lcd_is_cursor_enabled(void)
{
return lcd_cursor_enabled;
}
static void update_panel_size(struct fdt_lcd *config)
{
panel_info.vl_col = config->width;
panel_info.vl_row = config->height;
panel_info.vl_bpix = config->log2_bpp;
}
/*
* Main init function called by lcd driver.
* Inits and then prints test pattern if required.
*/
void lcd_ctrl_init(void *lcdbase)
{
struct fdt_lcd config;
int line_length, size;
/* get panel details */
if (fdt_decode_lcd(gd->blob, &config)) {
printf("No LCD information in device tree\n");
return;
}
/*
* The framebuffer address should be specified in the device tree.
* This FDT value should be the same as the one defined in Linux kernel;
* otherwise, it causes screen flicker. The FDT value overrides the
* framebuffer allocated at the top of memory by board_init_f().
*
* If the framebuffer address is not defined in the FDT, falls back to
* use the address allocated by board_init_f().
*/
if (config.frame_buffer != ADDR_T_NONE) {
gd->fb_base = config.frame_buffer;
lcd_base = (void *)(gd->fb_base);
} else {
config.frame_buffer = (u32)lcd_base;
}
update_panel_size(&config);
size = lcd_get_size(&line_length),
/* call board specific hw init */
init_lcd(&config);
/* For write-through or cache off, change the LCD memory region */
if (!(config.cache_type & FDT_LCD_CACHE_WRITE_BACK))
mmu_set_region_dcache(config.frame_buffer, size,
config.cache_type & FDT_LCD_CACHE_WRITE_THROUGH ?
DCACHE_WRITETHROUGH : DCACHE_OFF);
/* Enable flushing after LCD writes if requested */
lcd_set_flush_dcache(config.cache_type & FDT_LCD_CACHE_FLUSH);
debug("LCD frame buffer at %p\n", lcd_base);
}
ulong calc_fbsize(void)
{
return (panel_info.vl_col * panel_info.vl_row *
NBITS(panel_info.vl_bpix)) / 8;
}
void lcd_setcolreg(ushort regno, ushort red, ushort green, ushort blue)
{
}
void lcd_enable(void)
{
struct fdt_lcd config;
/* get panel details */
if (fdt_decode_lcd(gd->blob, &config)) {
printf("No LCD information in device tree\n");
return;
}
/* call board specific hw init for backlight PWM */
init_lcd_pwm(&config);
}
void lcd_early_init(const void *blob)
{
struct fdt_lcd config;
/* get panel details */
if (!fdt_decode_lcd(gd->blob, &config))
update_panel_size(&config);
}
int lcd_pinmux_early_init(const void *blob)
{
struct fdt_lcd config;
/* get panel details */
if (fdt_decode_lcd(gd->blob, &config))
return -1;
init_lcd_pinmux(&config);
return 0;
}