e9665959ed
Now that the console system itself will clearly differentiate loglevels, it is no longer necessary to explicitly add "ERROR: " in front of every BIOS_ERR message to help it stand out more (and allow automated tooling to grep for it). Removing all these extra .rodata characters should save us a nice little amount of binary size. This patch was created by running find src/ -type f -exec perl -0777 -pi -e 's/printk\(\s*BIOS_ERR,\s*"ERROR: /printk\(BIOS_ERR, "/gi' '{}' ';' and doing some cursory review/cleanup on the result. Then doing the same thing for BIOS_WARN with 's/printk\(\s*BIOS_WARNING,\s*"WARN(ING)?: /printk\(BIOS_WARNING, "/gi' Signed-off-by: Julius Werner <jwerner@chromium.org> Change-Id: I3d0573acb23d2df53db6813cb1a5fc31b5357db8 Reviewed-on: https://review.coreboot.org/c/coreboot/+/61309 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: HAOUAS Elyes <ehaouas@noos.fr> Reviewed-by: Lance Zhao Reviewed-by: Jason Glenesk <jason.glenesk@gmail.com>
1026 lines
25 KiB
C
1026 lines
25 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
|
|
#include <commonlib/helpers.h>
|
|
#include <console/console.h>
|
|
#include <device/mmio.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 <soc/display.h>
|
|
#include <soc/mipi_dsi.h>
|
|
#include <soc/mipi_display.h>
|
|
#include <soc/tegra_dsi.h>
|
|
#include <soc/mipi-phy.h>
|
|
#include <types.h>
|
|
|
|
#include "chip.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, (u32)bytes << 16, DSI_PKT_LEN_2_3);
|
|
tegra_dsi_writel(dsi, (u32)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 (tx && 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(struct device *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, "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.
|
|
*/
|
|
}
|