Skip to content
Snippets Groups Projects
Commit 0677ded8 authored by Gianfranco Mariotti's avatar Gianfranco Mariotti
Browse files

[DRIVER] video: add TI SN65DSI86 DSI to eDP bridge driver

parent d19e33e9
No related branches found
No related tags found
No related merge requests found
......@@ -1048,4 +1048,13 @@ config VIDEO_ADV7535
Say Y here if you want to enable support for ADI ADV7535
DSI to HDMI connector, currently only support 1920x1080.
config VIDEO_SN65DSI86
bool "TI SN65DSI86 DSI to eDP connector"
depends on DM_VIDEO
select VIDEO_MIPI_DSI
default n
help
Say Y here if you want to enable support for
TI SN65DSI86 DSI to eDP converter.
endmenu
......@@ -60,6 +60,7 @@ obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM67191) += raydium-rm67191.o
obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
obj-$(CONFIG_VIDEO_LCD_TDO_TL070WSH30) += tdo-tl070wsh30.o
obj-$(CONFIG_VIDEO_ADV7535) += adv7535.o
obj-$(CONFIG_VIDEO_SN65DSI86) += sn65dsi86.o
obj-${CONFIG_VIDEO_MESON} += meson/
obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
obj-$(CONFIG_VIDEO_IT6263_BRIDGE) += it6263_bridge.o
......
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2019 NXP
*
*/
#include <common.h>
#include <backlight.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <mipi_dsi.h>
#include <panel.h>
#include <asm/gpio.h>
#include <i2c.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <malloc.h>
#define usleep_range(a, b) udelay((b))
#define SN_DEVICE_REV_REG 0x08
#define SN_DPPLL_SRC_REG 0x0A
#define DPPLL_CLK_SRC_DSICLK BIT(0)
#define REFCLK_FREQ_MASK GENMASK(3, 1)
#define REFCLK_FREQ(x) ((x) << 1)
#define DPPLL_SRC_DP_PLL_LOCK BIT(7)
#define SN_PLL_ENABLE_REG 0x0D
#define SN_DSI_LANES_REG 0x10
#define CHA_DSI_LANES_MASK GENMASK(4, 3)
#define CHA_DSI_LANES(x) ((x) << 3)
#define CHB_DSI_LANES_MASK GENMASK(2, 1)
#define CHB_DSI_LANES(x) ((x) << 1)
#define SN_DSIA_CLK_FREQ_REG 0x12
#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG 0x20
#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG 0x24
#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG 0x2C
#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG 0x2D
#define CHA_HSYNC_POLARITY BIT(7)
#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG 0x30
#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG 0x31
#define CHA_VSYNC_POLARITY BIT(7)
#define SN_CHA_HORIZONTAL_BACK_PORCH_REG 0x34
#define SN_CHA_VERTICAL_BACK_PORCH_REG 0x36
#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG 0x38
#define SN_CHA_VERTICAL_FRONT_PORCH_REG 0x3A
#define SN_ENH_FRAME_REG 0x5A
#define VSTREAM_ENABLE BIT(3)
#define SN_DATA_FORMAT_REG 0x5B
#define SN_HPD_DISABLE_REG 0x5C
#define HPD_DISABLE BIT(0)
#define SN_AUX_WDATA0_REG 0x64
#define SN_AUX_ADDR_19_16_REG 0x74
#define SN_AUX_ADDR_15_8_REG 0x75
#define SN_AUX_ADDR_7_0_REG 0x76
#define SN_AUX_LENGTH_REG 0x77
#define SN_AUX_CMD_REG 0x78
#define AUX_CMD_SEND BIT(0)
#define AUX_CMD_REQ(x) ((x) << 4)
#define SN_SSC_CONFIG_REG 0x93
#define DP_NUM_LANES_MASK GENMASK(5, 4)
#define DP_NUM_LANES(x) ((x) << 4)
#define DP_SSC_SPREAD_MASK GENMASK(3, 1)
#define DP_SSC_SPREAD(x) ((x) << 1)
#define SN_DATARATE_CONFIG_REG 0x94
#define DP_DATARATE_MASK GENMASK(7, 5)
#define DP_DATARATE(x) ((x) << 5)
#define SN_ML_TX_MODE_REG 0x96
#define ML_TX_MAIN_LINK_OFF 0
#define ML_TX_NORMAL_MODE BIT(0)
#define MIN_DSI_CLK_FREQ_MHZ 40
/* fudge factor required to account for 8b/10b encoding */
#define DP_CLK_FUDGE_NUM 10
#define DP_CLK_FUDGE_DEN 8
struct sn65dsi86_priv {
struct udevice *dev;
struct udevice *backlight;
struct display_timing timing;
uint lanes;
enum mipi_dsi_pixel_format format;
unsigned long mode_flags;
};
static int ti_sn_bridge_write_mask(struct udevice *dev, uint addr, uint mask, uint8_t data)
{
uint8_t valb;
int err;
if (mask != 0xff) {
err = dm_i2c_read(dev, addr, &valb, 1);
if (err)
return err;
valb &= ~mask;
valb |= data;
} else {
valb = data;
}
err = dm_i2c_write(dev, addr, &valb, 1);
return err;
}
static int ti_sn_bridge_write(struct udevice *dev, uint addr, uint8_t data)
{
return ti_sn_bridge_write_mask(dev, addr, 0xff, data);
}
static void ti_sn_bridge_write_u16(struct udevice *dev, uint reg, uint16_t val)
{
ti_sn_bridge_write(dev, reg, val & 0xFF);
ti_sn_bridge_write(dev, reg + 1, val >> 8);
}
static __maybe_unused int ti_sn_bridge_read(struct udevice *dev, uint8_t addr, uint8_t *data)
{
uint8_t valb;
int err;
err = dm_i2c_read(dev, addr, &valb, 1);
if (err)
return err;
*data = (int)valb;
return 0;
}
/* clk frequencies supported by bridge in Hz in case derived from REFCLK pin */
static const u32 ti_sn_bridge_refclk_lut[] = {
12000000,
19200000,
26000000,
27000000,
38400000,
};
/* clk frequencies supported by bridge in Hz in case derived from DACP/N pin */
static const u32 ti_sn_bridge_dsiclk_lut[] = {
468000000,
384000000,
416000000,
486000000,
460800000,
};
static int sn65dsi86_pre_enable(struct udevice *dev)
{
struct sn65dsi86_priv *priv = dev_get_priv(dev);
u32 refclk_rate;
const u32 *refclk_lut;
u32 refclk_lut_size;
u32 bit_rate_khz, clk_freq_khz;
int i;
debug("%s\n", __func__);
if (dev_read_u32(dev, "refclk-frequency", &refclk_rate)) {
/* DP PLL from DACP/N */
bit_rate_khz = (priv->timing.pixelclock.typ / 1000) * mipi_dsi_pixel_format_to_bpp(priv->format);
clk_freq_khz = bit_rate_khz / (priv->lanes * 2);
refclk_rate = clk_freq_khz * 1000;
refclk_lut = ti_sn_bridge_dsiclk_lut;
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_dsiclk_lut);
} else {
/* DP PLL from REFCLK */
refclk_lut = ti_sn_bridge_refclk_lut;
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_refclk_lut);
}
/* for i equals to refclk_lut_size means default frequency */
for (i = 0; i < refclk_lut_size; i++)
if (refclk_lut[i] == refclk_rate)
break;
ti_sn_bridge_write_mask(dev, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK, REFCLK_FREQ(i));
/* HPD is not supported */
ti_sn_bridge_write_mask(dev, SN_HPD_DISABLE_REG, HPD_DISABLE, HPD_DISABLE);
usleep_range(300000, 300500); /* 10ms delay recommended by spec */
return 0;
}
/**
* LUT index corresponds to register value and
* LUT values corresponds to dp data rate supported
* by the bridge in Mbps unit.
*/
static const uint ti_sn_bridge_dp_rate_lut[] = {
0, 1620, 2160, 2430, 2700, 3240, 4320, 5400
};
static int sn65dsi86_enable(struct udevice *dev)
{
struct sn65dsi86_priv *priv = dev_get_priv(dev);
uint bit_rate_mhz, clk_freq_mhz, dp_rate_mhz;
uint8_t hsync_polarity = 0, vsync_polarity = 0;
int si_fields;
u32 si_reg, si_val;
uint8_t val;
int i;
debug("%s\n", __func__);
/* DSI_A lane config */
val = CHA_DSI_LANES(4 - priv->lanes);
ti_sn_bridge_write_mask(dev, SN_DSI_LANES_REG, CHA_DSI_LANES_MASK, val);
/* DSI_B lane config TODO - now always 0*/
val = CHB_DSI_LANES(0);
ti_sn_bridge_write_mask(dev, SN_DSI_LANES_REG, CHB_DSI_LANES_MASK, val);
/* DP lane config */
val = DP_NUM_LANES(priv->lanes - 2);
ti_sn_bridge_write_mask(dev, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, val);
/* DP lane SSC spread config */
val = DP_SSC_SPREAD(0);
ti_sn_bridge_write_mask(dev, SN_SSC_CONFIG_REG, DP_SSC_SPREAD_MASK, val);
/* set DSIA clk frequency */
bit_rate_mhz = ((priv->timing.pixelclock.typ / 1000) * mipi_dsi_pixel_format_to_bpp(priv->format)) / 1000;
clk_freq_mhz = bit_rate_mhz / (priv->lanes * 2);
/* for each increment in val, frequency increases by 5MHz */
val = (MIN_DSI_CLK_FREQ_MHZ / 5) +
(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF);
ti_sn_bridge_write(dev, SN_DSIA_CLK_FREQ_REG, val);
/* set DP data rate */
dp_rate_mhz = ((bit_rate_mhz / priv->lanes) * 2 * DP_CLK_FUDGE_NUM) / DP_CLK_FUDGE_DEN;
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++)
if (ti_sn_bridge_dp_rate_lut[i] > dp_rate_mhz)
break;
ti_sn_bridge_write_mask(dev, SN_DATARATE_CONFIG_REG, DP_DATARATE_MASK, DP_DATARATE(i));
/* Signal Integrity: fine-tune swing and pre-emphasis */
if (dev_read_prop(dev, "si-result", &si_fields)) {
si_fields /= sizeof(u32);
si_fields /= 2;
for (i = 0; i < si_fields; i++) {
dev_read_u32_index(dev, "si-result", 2*i, &si_reg);
dev_read_u32_index(dev, "si-result", 2*i+1, &si_val);
ti_sn_bridge_write(dev, si_reg, si_val & 0xFF);
}
}
/* enable DP PLL */
ti_sn_bridge_write(dev, SN_PLL_ENABLE_REG, 1);
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
/**
* The SN65DSI86 only supports ASSR Display Authentication method and
* this method is enabled by default. An eDP panel must support this
* authentication method. We need to enable this method in the eDP panel
* at DisplayPort address 0x0010A prior to link training.
*/
ti_sn_bridge_write(dev, SN_AUX_WDATA0_REG, 0x01);
ti_sn_bridge_write(dev, SN_AUX_ADDR_19_16_REG, 0x00);
ti_sn_bridge_write(dev, SN_AUX_ADDR_15_8_REG, 0x01);
ti_sn_bridge_write(dev, SN_AUX_ADDR_7_0_REG, 0x0A);
ti_sn_bridge_write(dev, SN_AUX_LENGTH_REG, 0x01);
ti_sn_bridge_write(dev, SN_AUX_CMD_REG, 0x81);
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
/* Semi auto link training mode */
ti_sn_bridge_write(dev, SN_ML_TX_MODE_REG, 0x0A);
udelay(20000); /* 20ms delay recommended by spec */
/* config video parameters */
if (priv->timing.flags & DISPLAY_FLAGS_HSYNC_HIGH)
hsync_polarity = CHA_HSYNC_POLARITY;
if (priv->timing.flags & DISPLAY_FLAGS_VSYNC_HIGH)
vsync_polarity = CHA_VSYNC_POLARITY;
ti_sn_bridge_write_u16(dev, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG, priv->timing.hactive.typ);
ti_sn_bridge_write_u16(dev, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG, priv->timing.vactive.typ);
ti_sn_bridge_write(dev, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG, priv->timing.hsync_len.typ & 0xFF);
ti_sn_bridge_write(dev, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG, ((priv->timing.hsync_len.typ >> 8) & 0x7F) | hsync_polarity);
ti_sn_bridge_write(dev, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG, priv->timing.vsync_len.typ & 0xFF);
ti_sn_bridge_write(dev, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG, ((priv->timing.vsync_len.typ >> 8) & 0x7F) | vsync_polarity);
ti_sn_bridge_write(dev, SN_CHA_HORIZONTAL_BACK_PORCH_REG, priv->timing.hback_porch.typ & 0xFF);
ti_sn_bridge_write(dev, SN_CHA_VERTICAL_BACK_PORCH_REG, priv->timing.vback_porch.typ & 0xFF);
ti_sn_bridge_write(dev, SN_CHA_HORIZONTAL_FRONT_PORCH_REG, priv->timing.hfront_porch.typ & 0xFF);
ti_sn_bridge_write(dev, SN_CHA_VERTICAL_FRONT_PORCH_REG, priv->timing.vfront_porch.typ & 0xFF);
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
/* enable video stream */
ti_sn_bridge_write_mask(dev, SN_ENH_FRAME_REG, VSTREAM_ENABLE, VSTREAM_ENABLE);
/* enable video test pattern */
if (dev_read_bool(dev, "test-mode"))
ti_sn_bridge_write(dev, 0x3c, 0xf0);
if (dev_read_bool(dev, "dump-regs")) {
// dump bridge regs
for (i = 0; i < 256; i++) {
ti_sn_bridge_read(dev, i, &val);
if (i%16 == 0)
printf("\n%02x: ", i);
printf("%02x ", val);
}
printf("\n");
}
return 0;
}
static int sn65dsi86_enable_backlight(struct udevice *dev)
{
struct sn65dsi86_priv *priv = dev_get_priv(dev);
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *device = plat->device;
int ret = 0;
debug("%s\n", __func__);
ret = mipi_dsi_attach(device);
if (ret < 0)
return ret;
if (priv->backlight)
return backlight_enable(priv->backlight);
return 0;
}
static int sn65dsi86_get_display_timing(struct udevice *dev,
struct display_timing *timing)
{
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
struct mipi_dsi_device *device = plat->device;
struct sn65dsi86_priv *priv = dev_get_priv(dev);
debug("%s\n", __func__);
memcpy(timing, &priv->timing, sizeof(*timing));
/* fill characteristics of DSI data link */
if (device) {
device->lanes = priv->lanes;
device->format = priv->format;
device->mode_flags = priv->mode_flags;
}
return 0;
}
static int sn65dsi86_probe(struct udevice *dev)
{
struct sn65dsi86_priv *priv = dev_get_priv(dev);
int err;
debug("%s\n", __func__);
if (dev_read_addr(dev) == 0)
return -ENODEV;
err = ofnode_decode_display_timing(dev_ofnode(dev), 0, &priv->timing);
if (err) {
dev_err(dev, "failed to get display timing\n");
return err;
}
debug("LCD: %dx%d, clk=%d Hz\n",
priv->timing.hactive.typ, priv->timing.vactive.typ,
priv->timing.pixelclock.typ);
debug(" hbp=%d, hfp=%d, hsw=%d\n",
priv->timing.hback_porch.typ, priv->timing.hfront_porch.typ,
priv->timing.hsync_len.typ);
debug(" vbp=%d, vfp=%d, vsw=%d\n",
priv->timing.vback_porch.typ, priv->timing.vfront_porch.typ,
priv->timing.vsync_len.typ);
priv->lanes = 4;
priv->format = MIPI_DSI_FMT_RGB888;
priv->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
err = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
"backlight", &priv->backlight);
if (err)
dev_warn(dev, "failed to get backlight\n");
sn65dsi86_pre_enable(dev);
sn65dsi86_enable(dev);
return 0;
}
static const struct panel_ops sn65dsi86_ops = {
.enable_backlight = sn65dsi86_enable_backlight,
.get_display_timing = sn65dsi86_get_display_timing,
};
static const struct udevice_id sn65dsi86_ids[] = {
{ .compatible = "ti,sn65dsi86" },
{ }
};
U_BOOT_DRIVER(ti_sn65dsi86) = {
.name = "ti_sn65dsi86",
.id = UCLASS_PANEL,
.of_match = sn65dsi86_ids,
.ops = &sn65dsi86_ops,
.probe = sn65dsi86_probe,
.plat_auto = sizeof(struct mipi_dsi_panel_plat),
.priv_auto = sizeof(struct sn65dsi86_priv),
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment