diff options
Diffstat (limited to 'src/soc/nvidia/tegra124/display.c')
-rw-r--r-- | src/soc/nvidia/tegra124/display.c | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/src/soc/nvidia/tegra124/display.c b/src/soc/nvidia/tegra124/display.c new file mode 100644 index 0000000000..b7d3d7f736 --- /dev/null +++ b/src/soc/nvidia/tegra124/display.c @@ -0,0 +1,304 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2013 Google Inc. + * + * 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; version 2 of the License. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <console/console.h> +#include <arch/io.h> +#include <stdint.h> +#include <lib.h> +#include <stdlib.h> +#include <delay.h> +#include <soc/addressmap.h> +#include <device/device.h> +#include <stdlib.h> +#include <string.h> +#include <cpu/cpu.h> +#include <boot/tables.h> +#include <cbmem.h> +#include <soc/clock.h> +#include <soc/nvidia/tegra/dc.h> +#include "clk_rst.h" +#include "chip.h" +#include <soc/display.h> + +static struct clk_rst_ctlr *clk_rst = (void *)TEGRA_CLK_RST_BASE; + +static const u32 rgb_enb_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_polarity_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x01000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_data_tab[PIN_REG_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, +}; + +static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00210222, + 0x00002200, + 0x00020000, +}; + +static int update_display_mode(struct dc_disp_reg *disp, + struct soc_nvidia_tegra124_config *config) +{ + u32 val; + u32 rate; + u32 div; + + writel(0x0, &disp->disp_timing_opt); + + writel(config->vref_to_sync << 16 | config->href_to_sync, + &disp->ref_to_sync); + writel(config->vsync_width << 16 | config->hsync_width, &disp->sync_width); + writel(config->vback_porch << 16 | config->hback_porch, &disp->back_porch); + writel(config->vfront_porch << 16 | config->hfront_porch, + &disp->front_porch); + + writel(config->xres | (config->yres << 16), &disp->disp_active); + + val = DE_SELECT_ACTIVE << DE_SELECT_SHIFT; + val |= DE_CONTROL_NORMAL << DE_CONTROL_SHIFT; + writel(val, &disp->data_enable_opt); + + val = DATA_FORMAT_DF1P1C << DATA_FORMAT_SHIFT; + val |= DATA_ALIGNMENT_MSB << DATA_ALIGNMENT_SHIFT; + val |= DATA_ORDER_RED_BLUE << DATA_ORDER_SHIFT; + writel(val, &disp->disp_interface_ctrl); + + /* + * The pixel clock divider is in 7.1 format (where the bottom bit + * represents 0.5). Here we calculate the divider needed to get from + * the display clock (typically 600MHz) to the pixel clock. We round + * up or down as requried. + * We use pllp for now. + */ + rate = 600 * 1000000; + div = ((rate * 2 + config->pixel_clock / 2) / config->pixel_clock) - 2; + printk(BIOS_SPEW, "Display clock %d, divider %d\n", rate, div); + + writel(0x00010001, &disp->shift_clk_opt); + + val = PIXEL_CLK_DIVIDER_PCD1 << PIXEL_CLK_DIVIDER_SHIFT; + val |= div << SHIFT_CLK_DIVIDER_SHIFT; + writel(val, &disp->disp_clk_ctrl); + + return 0; +} + +static int setup_window(struct disp_ctl_win *win, + struct soc_nvidia_tegra124_config *config) +{ + int log2_bpp = log2(config->framebuffer_bits_per_pixel); + win->x = 0; + win->y = 0; + win->w = config->xres; + win->h = config->yres; + win->out_x = 0; + win->out_y = 0; + win->out_w = config->xres; + win->out_h = config->yres; + win->phys_addr = config->framebuffer_base; + win->stride = config->xres * (1 << log2_bpp) / 8; + printk(BIOS_SPEW, "%s: depth = %d\n", __func__, log2_bpp); + switch (log2_bpp) { + case 5: + case 24: + win->fmt = COLOR_DEPTH_R8G8B8A8; + win->bpp = 32; + break; + case 4: + win->fmt = COLOR_DEPTH_B5G6R5; + win->bpp = 16; + break; + + default: + printk(BIOS_SPEW, "Unsupported LCD bit depth"); + return -1; + } + + return 0; +} + +static void update_window(struct display_controller *dc, + struct disp_ctl_win *win, + struct soc_nvidia_tegra124_config *config) +{ + u32 h_dda, v_dda; + u32 val; + + val = readl(&dc->cmd.disp_win_header); + val |= WINDOW_A_SELECT; + writel(val, &dc->cmd.disp_win_header); + + writel(win->fmt, &dc->win.color_depth); + + clrsetbits_le32(&dc->win.byte_swap, BYTE_SWAP_MASK, + BYTE_SWAP_NOSWAP << BYTE_SWAP_SHIFT); + + val = win->out_x << H_POSITION_SHIFT; + val |= win->out_y << V_POSITION_SHIFT; + writel(val, &dc->win.pos); + + val = win->out_w << H_SIZE_SHIFT; + val |= win->out_h << V_SIZE_SHIFT; + writel(val, &dc->win.size); + + val = (win->w * win->bpp / 8) << H_PRESCALED_SIZE_SHIFT; + val |= win->h << V_PRESCALED_SIZE_SHIFT; + writel(val, &dc->win.prescaled_size); + + writel(0, &dc->win.h_initial_dda); + writel(0, &dc->win.v_initial_dda); + + h_dda = (win->w * 0x1000) / MAX(win->out_w - 1, 1); + v_dda = (win->h * 0x1000) / MAX(win->out_h - 1, 1); + + val = h_dda << H_DDA_INC_SHIFT; + val |= v_dda << V_DDA_INC_SHIFT; + writel(val, &dc->win.dda_increment); + + writel(win->stride, &dc->win.line_stride); + writel(0, &dc->win.buf_stride); + + val = WIN_ENABLE; + if (win->bpp < 24) + val |= COLOR_EXPAND; + writel(val, &dc->win.win_opt); + + writel((u32) win->phys_addr, &dc->winbuf.start_addr); + writel(win->x, &dc->winbuf.addr_h_offset); + writel(win->y, &dc->winbuf.addr_v_offset); + + writel(0xff00, &dc->win.blend_nokey); + writel(0xff00, &dc->win.blend_1win); + + val = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + val |= GENERAL_UPDATE | WIN_A_UPDATE; + writel(val, &dc->cmd.state_ctrl); +} + +/* this is really aimed at the lcd panel. That said, there are two display + * devices on this part and we may someday want to extend it for other boards. + */ +void display_startup(device_t dev) +{ + u32 val; + int i; + struct soc_nvidia_tegra124_config *config = dev->chip_info; + struct display_controller *dc = (void *)config->display_controller; + struct disp_ctl_win window; + + /* should probably just make it all MiB ... in future */ + u32 framebuffer_size_mb = config->framebuffer_size / MiB; + u32 framebuffer_base_mb= config->framebuffer_base / MiB; + + printk(BIOS_SPEW, + "%s: xres %d yres %d framebuffer_bits_per_pixel %d\n", + __func__, + config->xres, config->yres, config->framebuffer_bits_per_pixel); + if (framebuffer_size_mb == 0){ + framebuffer_size_mb = ALIGN_UP(config->xres * config->yres * + (config->framebuffer_bits_per_pixel / 8), MiB)/MiB; + } + + if (! framebuffer_base_mb) + framebuffer_base_mb = FB_BASE_MB; + + mmu_config_range(framebuffer_base_mb, framebuffer_size_mb, + config->cache_policy); + + /* Enable flushing after LCD writes if requested */ + /* I don't understand this part yet. + lcd_set_flush_dcache(config.cache_type & FDT_LCD_CACHE_FLUSH); + */ + printk(BIOS_SPEW, "LCD frame buffer at %dMiB to %dMiB\n", framebuffer_base_mb, + framebuffer_base_mb + framebuffer_size_mb); + + /* GPIO magic here if needed to start powering up things. You + * really only want to enable vdd, wait a bit, and then enable + * the panel. However ... the timings in the tegra20 dts make + * no sense to me. I'm pretty sure they're wrong. + * The panel_vdd is done in the romstage, so we need only + * light things up here once we're sure it's all working. + */ + setbits_le32(&clk_rst->rst_dev_l, CLK_L_DISP1 | CLK_L_HOST1X); + + clock_ll_set_source_divisor(&clk_rst->clk_src_host1x, 4, + CLK_DIVIDER(TEGRA_PLLP_KHZ, 144000)); + /* u-boot uses PLLC for DISP1. + * But the u-boot code does not work and we don't set up PLLC anyway. + * PLLP seems quite good enough, so run with that for now. */ + clock_ll_set_source_divisor(&clk_rst->clk_src_disp1, 0 /* 4 */, + CLK_DIVIDER(TEGRA_PLLP_KHZ, 600000)); + + udelay(2); + + clrbits_le32(&clk_rst->rst_dev_l, CLK_L_DISP1|CLK_L_HOST1X); + + writel(0x00000100, &dc->cmd.gen_incr_syncpt_ctrl); + writel(0x0000011a, &dc->cmd.cont_syncpt_vsync); + writel(0x00000000, &dc->cmd.int_type); + writel(0x00000000, &dc->cmd.int_polarity); + writel(0x00000000, &dc->cmd.int_mask); + writel(0x00000000, &dc->cmd.int_enb); + + val = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE; + val |= PW3_ENABLE | PW4_ENABLE | PM0_ENABLE; + val |= PM1_ENABLE; + writel(val, &dc->cmd.disp_pow_ctrl); + + val = readl(&dc->cmd.disp_cmd); + val |= CTRL_MODE_C_DISPLAY << CTRL_MODE_SHIFT; + writel(val, &dc->cmd.disp_cmd); + + writel(0x00000020, &dc->disp.mem_high_pri); + writel(0x00000001, &dc->disp.mem_high_pri_timer); + + for (i = 0; i < PIN_REG_COUNT; i++) { + writel(rgb_enb_tab[i], &dc->com.pin_output_enb[i]); + writel(rgb_polarity_tab[i], &dc->com.pin_output_polarity[i]); + writel(rgb_data_tab[i], &dc->com.pin_output_data[i]); + } + + for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++) + writel(rgb_sel_tab[i], &dc->com.pin_output_sel[i]); + + if (config->pixel_clock) + update_display_mode(&dc->disp, config); + + if (!setup_window(&window, config)) + update_window(dc, &window, config); + +} + |