diff options
author | Patrick Georgi <pgeorgi@chromium.org> | 2015-06-22 19:41:29 +0200 |
---|---|---|
committer | Patrick Georgi <pgeorgi@google.com> | 2015-06-30 21:43:01 +0200 |
commit | 40a3e321d4e8f2877de1700db67b8c7f7ea89820 (patch) | |
tree | b8270b2ceb9e290d2e4e9a99868acb9cd335de6f /src/soc/nvidia/tegra210/dsi.c | |
parent | 7f641e68f25c0b79960a97a6b265851c46298aae (diff) | |
download | coreboot-40a3e321d4e8f2877de1700db67b8c7f7ea89820.tar.xz |
nvidia/tegra210: add new SoC
This includes Chrome OS downstream up to Change-Id: Ic89ed54c.
Change-Id: I81853434600390d643160fe57554495b2bfe60ab
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Reviewed-on: http://review.coreboot.org/10633
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'src/soc/nvidia/tegra210/dsi.c')
-rw-r--r-- | src/soc/nvidia/tegra210/dsi.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/soc/nvidia/tegra210/dsi.c b/src/soc/nvidia/tegra210/dsi.c new file mode 100644 index 0000000000..3d0b8502b1 --- /dev/null +++ b/src/soc/nvidia/tegra210/dsi.c @@ -0,0 +1,1045 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2014 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. + */ +#include <console/console.h> +#include <arch/io.h> +#include <stdint.h> +#include <lib.h> +#include <stdlib.h> +#include <delay.h> +#include <timer.h> +#include <soc/addressmap.h> +#include <soc/clock.h> +#include <device/device.h> +#include <edid.h> +#include <soc/nvidia/tegra/types.h> +#include <soc/nvidia/tegra/dc.h> +#include "chip.h" +#include <soc/display.h> +#include <soc/mipi_dsi.h> +#include <soc/mipi_display.h> +#include <soc/tegra_dsi.h> +#include <soc/mipi-phy.h> +#include "jdi_25x18_display/panel-jdi-lpm102a188a.h" + +struct tegra_mipi_device mipi_device_data[NUM_DSI]; + +struct tegra_dsi dsi_data[NUM_DSI] = { + { + .regs = (void *)TEGRA_DSIA_BASE, + .channel = 0, + .slave = &dsi_data[DSI_B], + .master = NULL, + .video_fifo_depth = MAX_DSI_VIDEO_FIFO_DEPTH, + .host_fifo_depth = MAX_DSI_HOST_FIFO_DEPTH, + }, + { + .regs = (void *)TEGRA_DSIB_BASE, + .channel = 0, + .slave = NULL, + .master = &dsi_data[DSI_A], + .video_fifo_depth = MAX_DSI_VIDEO_FIFO_DEPTH, + .host_fifo_depth = MAX_DSI_HOST_FIFO_DEPTH, + }, +}; + +static const u32 init_reg[] = { + DSI_INT_ENABLE, + DSI_INT_STATUS, + DSI_INT_MASK, + DSI_INIT_SEQ_DATA_0, + DSI_INIT_SEQ_DATA_1, + DSI_INIT_SEQ_DATA_2, + DSI_INIT_SEQ_DATA_3, + DSI_INIT_SEQ_DATA_4, + DSI_INIT_SEQ_DATA_5, + DSI_INIT_SEQ_DATA_6, + DSI_INIT_SEQ_DATA_7, + DSI_INIT_SEQ_DATA_15, + DSI_DCS_CMDS, + DSI_PKT_SEQ_0_LO, + DSI_PKT_SEQ_1_LO, + DSI_PKT_SEQ_2_LO, + DSI_PKT_SEQ_3_LO, + DSI_PKT_SEQ_4_LO, + DSI_PKT_SEQ_5_LO, + DSI_PKT_SEQ_0_HI, + DSI_PKT_SEQ_1_HI, + DSI_PKT_SEQ_2_HI, + DSI_PKT_SEQ_3_HI, + DSI_PKT_SEQ_4_HI, + DSI_PKT_SEQ_5_HI, + DSI_CONTROL, + DSI_HOST_CONTROL, + DSI_PAD_CONTROL_0, + DSI_PAD_CONTROL_CD, + DSI_SOL_DELAY, + DSI_MAX_THRESHOLD, + DSI_TRIGGER, + DSI_TX_CRC, + DSI_INIT_SEQ_CONTROL, + DSI_PKT_LEN_0_1, + DSI_PKT_LEN_2_3, + DSI_PKT_LEN_4_5, + DSI_PKT_LEN_6_7, +}; + +static inline struct tegra_dsi *host_to_tegra(struct mipi_dsi_host *host) +{ + return container_of(host, struct tegra_dsi, host); +} + +/* + * non-burst mode with sync pulses + */ +static const u32 pkt_seq_video_non_burst_sync_pulses[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), +}; + +/* + * non-burst mode with sync events + */ +static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 5] = 0, + + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_END_OF_TRANSMISSION) | PKT_LEN1(7) | + PKT_LP, + [ 9] = 0, + + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(2) | + PKT_ID2(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN2(3), + + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), +}; + +static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { + [ 0] = 0, + [ 1] = 0, + [ 2] = 0, + [ 3] = 0, + [ 4] = 0, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP, + [ 7] = 0, + [ 8] = 0, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP, + [11] = 0, +}; + +static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) +{ + int err; + + err = mipi_dphy_set_timing(dsi); + if (err < 0) { + printk(BIOS_ERR, "failed to set D-PHY timing: %d\n", err); + return err; + } + + if (dsi->slave) + tegra_dsi_set_phy_timing(dsi->slave); + return 0; +} + +static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, + unsigned int *mulp, unsigned int *divp) +{ + switch (format) { + case MIPI_DSI_FMT_RGB666_PACKED: + case MIPI_DSI_FMT_RGB888: + *mulp = 3; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB565: + *mulp = 2; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB666: + *mulp = 9; + *divp = 4; + break; + + default: + return -EINVAL; + } + return 0; +} + +static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, + enum tegra_dsi_format *fmt) +{ + switch (format) { + case MIPI_DSI_FMT_RGB888: + *fmt = TEGRA_DSI_FORMAT_24P; + break; + + case MIPI_DSI_FMT_RGB666: + *fmt = TEGRA_DSI_FORMAT_18NP; + break; + + case MIPI_DSI_FMT_RGB666_PACKED: + *fmt = TEGRA_DSI_FORMAT_18P; + break; + + case MIPI_DSI_FMT_RGB565: + *fmt = TEGRA_DSI_FORMAT_16P; + break; + + default: + return -EINVAL; + } + return 0; +} + +static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start, + unsigned int size) +{ + u32 value; + + tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE); + + value = DSI_GANGED_MODE_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL); +} + +static void tegra_dsi_enable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + if (dsi->slave) + tegra_dsi_enable(dsi->slave); +} + +static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, + const struct soc_nvidia_tegra210_config *mode) +{ + unsigned int hact, hsw, hbp, hfp, i, mul, div; + enum tegra_dsi_format format; + const u32 *pkt_seq; + u32 value; + int err; + + if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { + printk(BIOS_SPEW, "Non-burst video mode with sync pulses\n"); + pkt_seq = pkt_seq_video_non_burst_sync_pulses; + } else if (dsi->flags & MIPI_DSI_MODE_VIDEO) { + printk(BIOS_SPEW, "Non-burst video mode with sync events\n"); + pkt_seq = pkt_seq_video_non_burst_sync_events; + } else { + printk(BIOS_SPEW, "Command mode\n"); + pkt_seq = pkt_seq_command_mode; + } + + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); + if (err < 0) + return err; + + err = tegra_dsi_get_format(dsi->format, &format); + if (err < 0) + return err; + + value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | + DSI_CONTROL_LANES(dsi->lanes - 1) | + DSI_CONTROL_SOURCE(pipe); + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD); + + value = DSI_HOST_CONTROL_HS; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + value = tegra_dsi_readl(dsi, DSI_CONTROL); + + if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) + value |= DSI_CONTROL_HS_CLK_CTRL; + + value &= ~DSI_CONTROL_TX_TRIG(3); + + /* enable DCS commands for command mode */ + if (dsi->flags & MIPI_DSI_MODE_VIDEO) + value &= ~DSI_CONTROL_DCS_ENABLE; + else + value |= DSI_CONTROL_DCS_ENABLE; + + value |= DSI_CONTROL_VIDEO_ENABLE; + value &= ~DSI_CONTROL_HOST_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + for (i = 0; i < NUM_PKT_SEQ; i++) + tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i); + + if (dsi->flags & MIPI_DSI_MODE_VIDEO) { + /* horizontal active pixels */ + hact = mode->xres * mul / div; + + /* horizontal sync width */ + hsw = (hsync_end(mode) - hsync_start(mode)) * mul / div; + + /* horizontal back porch */ + hbp = (htotal(mode) - hsync_end(mode)) * mul / div; + if ((dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) == 0) + hbp += hsw; + + /* horizontal front porch */ + hfp = (hsync_start(mode) - mode->xres) * mul / div; + + /* subtract packet overhead */ + hsw -= 10; + hbp -= 14; + hfp -= 8; + + tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); + + /* set SOL delay (for non-burst mode only) */ + tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + + /* TODO: implement ganged mode */ + } else { + u16 bytes; + if (dsi->ganged_mode) { + /* + * For ganged mode, assume symmetric left-right mode. + */ + bytes = 1 + (mode->xres / 2) * mul / div; + } else { + /* 1 byte (DCS command) + pixel data */ + bytes = 1 + mode->xres * mul / div; + } + + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7); + + value = MIPI_DCS_WRITE_MEMORY_START << 8 | + MIPI_DCS_WRITE_MEMORY_CONTINUE; + tegra_dsi_writel(dsi, value, DSI_DCS_CMDS); + + /* set SOL delay */ + if (dsi->ganged_mode) { + unsigned long delay, bclk, bclk_ganged; + unsigned int lanes = dsi->ganged_lanes; + + /* SOL to valid, valid to FIFO and FIFO write delay */ + delay = 4 + 4 + 2; + delay = DIV_ROUND_UP(delay * mul, div * lanes); + /* FIFO read delay */ + delay = delay + 6; + + bclk = DIV_ROUND_UP(htotal(mode) * mul, div * lanes); + bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes); + value = bclk - bclk_ganged + delay + 20; + } else { + /* TODO: revisit for non-ganged mode */ + value = 8 * mul / div; + } + + tegra_dsi_writel(dsi, value, DSI_SOL_DELAY); + } + + if (dsi->slave) { + err = tegra_dsi_configure(dsi->slave, pipe, mode); + if (err < 0) + return err; + + /* + * enable ganged mode + */ + if (dsi->ganged_mode) { + tegra_dsi_ganged_enable(dsi, mode->xres / 2, + mode->xres / 2); + tegra_dsi_ganged_enable(dsi->slave, 0, mode->xres / 2); + } + } + return 0; +} + +static int tegra_output_dsi_enable(struct tegra_dsi *dsi, + const struct soc_nvidia_tegra210_config *config) +{ + int err; + + if (dsi->enabled) + return 0; + + err = tegra_dsi_configure(dsi, 0, config); + if (err < 0) { + printk(BIOS_ERR, "DSI configuration failed\n"); + return err; + } + + /* enable DSI controller */ + tegra_dsi_enable(dsi); + + dsi->enabled = true; + return 0; +} + + +static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, + unsigned int vrefresh) +{ + unsigned int timeout; + u32 value; + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + + if (dsi->slave) + tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh); +} + +static int tegra_output_dsi_setup_clock(struct tegra_dsi *dsi, + const struct soc_nvidia_tegra210_config *config) +{ + unsigned int mul, div, num_lanes; + unsigned long bclk; + unsigned long pclk = config->pixel_clock; + int plld; + int err; + struct display_controller *disp_ctrl = + (void *)config->display_controller; + unsigned int shift_clk_div; + + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); + if (err < 0) + return err; + + /* + * In ganged mode, account for the total number of lanes across both + * DSI channels so that the bit clock is properly computed. + */ + if (dsi->ganged_mode) + num_lanes = dsi->ganged_lanes; + else + num_lanes = dsi->lanes; + + /* compute byte clock */ + bclk = (pclk * mul) / (div * num_lanes); + + /* + * Compute bit clock and round up to the next MHz. + */ + plld = DIV_ROUND_UP(bclk * 8, USECS_PER_SEC) * USECS_PER_SEC; + + /* + * the actual rate on PLLD_OUT0 is 1/2 plld + */ + dsi->clk_rate = plld / 2; + if (dsi->slave) + dsi->slave->clk_rate = dsi->clk_rate; + + /* set up plld */ + plld = clock_configure_plld(plld); + if (plld == 0) { + printk(BIOS_ERR, "%s: clock init failed\n", __func__); + return -1; + } else + printk(BIOS_INFO, "%s: plld is configured to: %u\n", + __func__, plld); + + /* + * Derive pixel clock from bit clock using the shift clock divider. + * Note that this is only half of what we would expect, but we need + * that to make up for the fact that we divided the bit clock by a + * factor of two above. + */ + shift_clk_div = ((8 * mul) / (div * num_lanes)) - 2; + update_display_shift_clock_divider(disp_ctrl, shift_clk_div); + + tegra_dsi_set_timeout(dsi, bclk, config->refresh); + return plld/1000000; +} + + + +static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) +{ + unsigned long value; + + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + return 0; +} + +static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) +{ + u32 value; + + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); + + /* start calibration */ + tegra_dsi_pad_enable(dsi); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3); + + return tegra_mipi_calibrate(dsi->mipi); +} + +static const char * const error_report[16] = { + "SoT Error", + "SoT Sync Error", + "EoT Sync Error", + "Escape Mode Entry Command Error", + "Low-Power Transmit Sync Error", + "Peripheral Timeout Error", + "False Control Error", + "Contention Detected", + "ECC Error, single-bit", + "ECC Error, multi-bit", + "Checksum Error", + "DSI Data Type Not Recognized", + "DSI VC ID Invalid", + "Invalid Transmission Length", + "Reserved", + "DSI Protocol Violation", +}; + +static int tegra_dsi_read_response(struct tegra_dsi *dsi, + const struct mipi_dsi_msg *msg, + unsigned int count) +{ + u8 *rx = msg->rx_buf; + unsigned int i, j, k; + size_t size = 0; + u16 errors; + u32 value; + + /* read and parse packet header */ + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + switch (value & 0x3f) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + errors = (value >> 8) & 0xffff; + printk(BIOS_ERR, "Acknowledge and error report: %04x\n", + errors); + for (i = 0; i < ARRAY_SIZE(error_report); i++) + if (errors & BIT(i)) + printk(BIOS_INFO, " %2u: %s\n", i, + error_report[i]); + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rx[0] = (value >> 8) & 0xff; + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + rx[0] = (value >> 8) & 0xff; + rx[1] = (value >> 16) & 0xff; + break; + + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + default: + printk(BIOS_ERR, "unhandled response type: %02x\n", + value & 0x3f); + break; + } + + size = MIN(size, msg->rx_len); + + if (msg->rx_buf && size > 0) { + for (i = 0, j = 0; i < count - 1; i++, j += 4) { + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + for (k = 0; k < 4 && (j + k) < msg->rx_len; k++) + rx[j + k] = (value >> (k << 3)) & 0xff; + } + } + return 0; +} + +static int tegra_dsi_transmit(struct tegra_dsi *dsi, unsigned long timeout_ms) +{ + u32 poll_interval_us = 2000; + u32 timeout_us = timeout_ms * 1000; + + tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER); + udelay(poll_interval_us); + + do { + u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER); + if ((value & DSI_TRIGGER_HOST) == 0) + return 0; + + //usleep_range(1000, 2000); + if (timeout_us > poll_interval_us) + timeout_us -= poll_interval_us; + else + break; + + udelay(poll_interval_us); + } while (1); + + printk(BIOS_ERR, "%s: ERROR: timeout waiting for transmission" + " to complete\n", __func__); + return -ETIMEDOUT; +} + +static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi, + unsigned long timeout_ms) +{ + u32 poll_interval_us = 2000; + u32 timeout_us = timeout_ms * 1000; + + do { + u32 value = tegra_dsi_readl(dsi, DSI_STATUS); + u8 count = value & 0x1f; + + if (count > 0) + return count; + + if (timeout_us > poll_interval_us) + timeout_us -= poll_interval_us; + else + break; + + udelay(poll_interval_us); + } while (1); + + printk(BIOS_ERR, "%s: ERROR: timeout\n", __func__); + + return -ETIMEDOUT; +} + +static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + const u8 *tx = msg->tx_buf; + unsigned int count, i, j; + u32 value; + int err; + + if (msg->tx_len > dsi->video_fifo_depth * 4) + return -ENOSPC; + + /* reset underflow/overflow flags */ + value = tegra_dsi_readl(dsi, DSI_STATUS); + if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) { + value = DSI_HOST_CONTROL_FIFO_RESET; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + udelay(20); // usleep_range(10, 20); + } + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + udelay(7000); //usleep_range(5000, 10000); + + value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | + DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0) + value |= DSI_HOST_CONTROL_HS; + + /* + * The host FIFO has a maximum of 64 words, so larger transmissions + * need to use the video FIFO. + */ + if (msg->tx_len > dsi->host_fifo_depth * 4) + value |= DSI_HOST_CONTROL_FIFO_SEL; + + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + /* + * For reads and messages with explicitly requested ACK, generate a + * BTA sequence after the transmission of the packet. + */ + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL); + value |= DSI_HOST_CONTROL_PKT_BTA; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + } + + value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + /* write packet header */ + value = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f); + + if (tx && msg->tx_len > 0) + value |= tx[0] << 8; + + if (tx && msg->tx_len > 1) + value |= tx[1] << 16; + + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + + /* write payload (if any) */ + if (msg->tx_len > 2) { + for (j = 2; j < msg->tx_len; j += 4) { + value = 0; + + for (i = 0; i < 4 && j + i < msg->tx_len; i++) + value |= tx[j + i] << (i << 3); + + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + } + } + + err = tegra_dsi_transmit(dsi, 250); + if (err < 0) { + printk(BIOS_INFO, "Failed to transmit. %d\n", err); + return err; + } + + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + err = tegra_dsi_wait_for_response(dsi, 250); + if (err < 0) { + printk(BIOS_INFO, "Failed to read response. %d\n", err); + return err; + } + count = err; + + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + switch (value) { + case 0x84: + printk(BIOS_INFO, "ACK\n"); + break; + + case 0x87: + printk(BIOS_INFO, "ESCAPE\n"); + break; + + default: + printk(BIOS_INFO, "unknown status: %08x\n", value); + break; + } + + if (count > 1) { + err = tegra_dsi_read_response(dsi, msg, count); + if (err < 0) + printk(BIOS_INFO, + "failed to parse response: %d\n", + err); + else { + /* + * For read commands, return the number of + * bytes returned by the peripheral. + */ + count = err; + } + } + } else { + /* + * For write commands, we have transmitted the 4-byte header + * plus the variable-length payload. + */ + count = 4 + msg->tx_len; + } + + return count; +} + +static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi, + struct tegra_dsi *slave) +{ + /* + * The number of ganged lanes is the sum of lanes of all peripherals + * in the gang. + */ + dsi->slave->ganged_lanes = dsi->lanes + dsi->slave->lanes; + dsi->slave->ganged_mode = 1; + + dsi->ganged_lanes = dsi->lanes + dsi->slave->lanes; + dsi->ganged_mode = 1; + return 0; +} + +static int tegra_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + int err; + + dsi->flags = device->mode_flags; + dsi->format = device->format; + dsi->lanes = device->lanes; + + if (dsi->master) { + err = tegra_dsi_ganged_setup(dsi->master, dsi); + if (err < 0) { + printk(BIOS_ERR, "failed to set up ganged mode: %d\n", + err); + return err; + } + } + return 0; +} + +static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { + .attach = tegra_dsi_host_attach, + .transfer = tegra_dsi_host_transfer, +}; + +static int dsi_probe_if(int dsi_index, + struct soc_nvidia_tegra210_config *config) +{ + struct tegra_dsi *dsi = &dsi_data[dsi_index]; + int err; + + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL); + + /* + * Set default value. Will be taken from attached device once detected + */ + dsi->flags = 0; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + /* get tegra_mipi_device */ + dsi->mipi = tegra_mipi_request(&mipi_device_data[dsi_index], dsi_index); + + /* calibrate */ + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) { + printk(BIOS_ERR, "MIPI calibration failed: %d\n", err); + return err; + } + + dsi->host.ops = &tegra_dsi_host_ops; + err = mipi_dsi_host_register(&dsi->host); + if (err < 0) { + printk(BIOS_ERR, "failed to register DSI host: %d\n", err); + return err; + } + + /* get panel */ + dsi->panel = panel_jdi_dsi_probe((struct mipi_dsi_device *)dsi->host.dev); + if (IS_ERR_PTR(dsi->panel)) { + printk(BIOS_ERR, "failed to get dsi panel\n"); + return -EPTR; + } + dsi->panel->mode = config; + return 0; +} + +static int dsi_probe(struct soc_nvidia_tegra210_config *config) +{ + dsi_probe_if(DSI_A, config); + dsi_probe_if(DSI_B, config); + return 0; +} + +static void tegra_dsi_init_regs(struct tegra_dsi *dsi) +{ + int i; + for (i = 0; i < ARRAY_SIZE(init_reg); i++) + tegra_dsi_writel(dsi, 0, init_reg[i]); + + if (dsi->slave) + tegra_dsi_init_regs(dsi->slave); +} + +static int dsi_enable(struct soc_nvidia_tegra210_config *config) +{ + struct tegra_dsi *dsi_a = &dsi_data[DSI_A]; + + dsi_probe(config); + + /* set up clock and TimeOutRegisters */ + tegra_output_dsi_setup_clock(dsi_a, config); + + /* configure APB_MISC_GP_MIPI_PAD_CTRL_0 */ + write32((unsigned int *)APB_MISC_GP_MIPI_PAD_CTRL_0, DSIB_MODE_DSI); + + /* configure phy interface timing registers */ + tegra_dsi_set_phy_timing(dsi_a); + + /* Initialize DSI registers */ + tegra_dsi_init_regs(dsi_a); + + /* prepare panel */ + panel_jdi_prepare(dsi_a->panel); + + /* enable dsi */ + if (tegra_output_dsi_enable(dsi_a, config)) { + printk(BIOS_ERR,"%s: Error: failed to enable dsi output.\n", + __func__); + return -1; + } + + return 0; +} + +void dsi_display_startup(device_t dev) +{ + struct soc_nvidia_tegra210_config *config = dev->chip_info; + struct display_controller *disp_ctrl = + (void *)config->display_controller; + u32 plld_rate; + + u32 framebuffer_size_mb = config->framebuffer_size / MiB; + u32 framebuffer_base_mb= config->framebuffer_base / MiB; + + printk(BIOS_INFO, "%s: entry: disp_ctrl: %p.\n", + __func__, disp_ctrl); + + if (disp_ctrl == NULL) { + printk(BIOS_ERR, "Error: No dc is assigned by dt.\n"); + return; + } + + if (framebuffer_size_mb == 0){ + framebuffer_size_mb = ALIGN_UP(config->display_xres * + config->display_yres * + (config->framebuffer_bits_per_pixel / 8), MiB)/MiB; + } + + config->framebuffer_size = framebuffer_size_mb * MiB; + config->framebuffer_base = framebuffer_base_mb * MiB; + + /* + * The plld is programmed with the assumption of the SHIFT_CLK_DIVIDER + * and PIXEL_CLK_DIVIDER are zero (divide by 1). See the + * update_display_mode() for detail. + */ + /* set default plld */ + plld_rate = clock_configure_plld(config->pixel_clock * 2); + if (plld_rate == 0) { + printk(BIOS_ERR, "dc: clock init failed\n"); + return; + } + + /* set disp1's clock source to PLLD_OUT0 */ + clock_configure_source(disp1, PLLD_OUT0, (plld_rate/KHz)); + + /* Init dc */ + if (tegra_dc_init(disp_ctrl)) { + printk(BIOS_ERR, "dc: init failed\n"); + return; + } + + /* Configure dc mode */ + if (update_display_mode(disp_ctrl, config)) { + printk(BIOS_ERR, "dc: failed to configure display mode.\n"); + return; + } + + /* Configure and enable dsi controller and panel */ + if (dsi_enable(config)) { + printk(BIOS_ERR, "%s: failed to enable dsi controllers.\n", + __func__); + return; + } + + /* Set up window */ + update_window(config); + printk(BIOS_INFO, "%s: display init done.\n", __func__); + + /* Save panel information to cb tables */ + pass_mode_info_to_payload(config); + + /* + * After this point, it is payload's responsibility to allocate + * framebuffer and sets the base address to dc's + * WINBUF_START_ADDR register and enables window by setting dc's + * DISP_DISP_WIN_OPTIONS register. + */ +} |