mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-11 22:03:15 +00:00
b8d3a6c7d1
Perform removal of DSS if kconfigs VIDEO_REMOVE or SPL_VIDEO_REMOVE is set by user. Otherwise if above Kconfigs are not selected, it is assumed that user wants splash screen to be displayed until linux kernel boots up. In such scenario, leave the power domain of DSS as "on" so that splash screen stays intact until kernel boots up. Signed-off-by: Nikhil M Jain <n-jain1@ti.com> Reviewed-by: Devarsh Thakkar <devarsht@ti.com>
939 lines
23 KiB
C
939 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2023 Texas Instruments Incorporated - https://www.ti.com/
|
|
* Nikhil M Jain, n-jain1@ti.com
|
|
*
|
|
* based on the linux tidss driver, which is
|
|
*
|
|
* (C) Copyright 2018 Texas Instruments Incorporated - https://www.ti.com/
|
|
* Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <dm.h>
|
|
#include <clk.h>
|
|
#include <log.h>
|
|
#include <video.h>
|
|
#include <errno.h>
|
|
#include <panel.h>
|
|
#include <reset.h>
|
|
#include <malloc.h>
|
|
#include <fdtdec.h>
|
|
#include <syscon.h>
|
|
#include <regmap.h>
|
|
#include <cpu_func.h>
|
|
#include <media_bus_format.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/cache.h>
|
|
#include <asm/utils.h>
|
|
#include <asm/bitops.h>
|
|
|
|
#include <dm/devres.h>
|
|
#include <dm/of_access.h>
|
|
#include <dm/device_compat.h>
|
|
#include <dm/device-internal.h>
|
|
|
|
#include <linux/bug.h>
|
|
#include <linux/err.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/iopoll.h>
|
|
|
|
#include "tidss_drv.h"
|
|
#include "tidss_regs.h"
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
/* Panel parameters */
|
|
enum {
|
|
LCD_MAX_WIDTH = 1920,
|
|
LCD_MAX_HEIGHT = 1200,
|
|
LCD_MAX_LOG2_BPP = VIDEO_BPP32,
|
|
};
|
|
|
|
static const u16 *dss_common_regmap;
|
|
|
|
static const u16 tidss_am62x_common_regs[DSS_COMMON_REG_TABLE_LEN] = {
|
|
[DSS_REVISION_OFF] = 0x4,
|
|
[DSS_SYSCONFIG_OFF] = 0x8,
|
|
[DSS_SYSSTATUS_OFF] = 0x20,
|
|
[DSS_IRQ_EOI_OFF] = 0x24,
|
|
[DSS_IRQSTATUS_RAW_OFF] = 0x28,
|
|
[DSS_IRQSTATUS_OFF] = 0x2c,
|
|
[DSS_IRQENABLE_SET_OFF] = 0x30,
|
|
[DSS_IRQENABLE_CLR_OFF] = 0x40,
|
|
[DSS_VID_IRQENABLE_OFF] = 0x44,
|
|
[DSS_VID_IRQSTATUS_OFF] = 0x58,
|
|
[DSS_VP_IRQENABLE_OFF] = 0x70,
|
|
[DSS_VP_IRQSTATUS_OFF] = 0x7c,
|
|
|
|
[WB_IRQENABLE_OFF] = 0x88,
|
|
[WB_IRQSTATUS_OFF] = 0x8c,
|
|
|
|
[DSS_GLOBAL_MFLAG_ATTRIBUTE_OFF] = 0x90,
|
|
[DSS_GLOBAL_OUTPUT_ENABLE_OFF] = 0x94,
|
|
[DSS_GLOBAL_BUFFER_OFF] = 0x98,
|
|
[DSS_CBA_CFG_OFF] = 0x9c,
|
|
[DSS_DBG_CONTROL_OFF] = 0xa0,
|
|
[DSS_DBG_STATUS_OFF] = 0xa4,
|
|
[DSS_CLKGATING_DISABLE_OFF] = 0xa8,
|
|
[DSS_SECURE_DISABLE_OFF] = 0xac,
|
|
};
|
|
|
|
/* TIDSS AM62x Features */
|
|
const struct dss_features dss_am625_feats = {
|
|
.max_pclk_khz = {
|
|
[DSS_VP_DPI] = 165000,
|
|
[DSS_VP_OLDI] = 165000,
|
|
},
|
|
|
|
.subrev = DSS_AM625,
|
|
|
|
.common = "common",
|
|
.common_regs = tidss_am62x_common_regs,
|
|
|
|
.num_vps = 2,
|
|
.vp_name = { "vp1", "vp2" },
|
|
.ovr_name = { "ovr1", "ovr2" },
|
|
.vpclk_name = { "vp1", "vp2" },
|
|
.vp_bus_type = { DSS_VP_OLDI, DSS_VP_DPI },
|
|
|
|
.vp_feat = { .color = {
|
|
.has_ctm = true,
|
|
.gamma_size = 256,
|
|
.gamma_type = TIDSS_GAMMA_8BIT,
|
|
},
|
|
},
|
|
|
|
.num_planes = 2,
|
|
/* note: vid is plane_id 0 and vidl1 is plane_id 1 */
|
|
.vid_name = { "vidl1", "vid1" },
|
|
.vid_lite = { true, false },
|
|
.vid_order = { 1, 0 },
|
|
};
|
|
|
|
/* Wrapper functions to write and read TI_DSS registers */
|
|
static void dss_write(struct tidss_drv_priv *priv, u16 reg, u32 val)
|
|
{
|
|
writel(val, priv->base_common + reg);
|
|
}
|
|
|
|
static u32 dss_read(struct tidss_drv_priv *priv, u16 reg)
|
|
{
|
|
return readl(priv->base_common + reg);
|
|
}
|
|
|
|
static void dss_vid_write(struct tidss_drv_priv *priv, u32 hw_plane, u16 reg, u32 val)
|
|
{
|
|
void __iomem *base = priv->base_vid[hw_plane];
|
|
|
|
writel(val, base + reg);
|
|
}
|
|
|
|
static u32 dss_vid_read(struct tidss_drv_priv *priv, u32 hw_plane, u16 reg)
|
|
{
|
|
void __iomem *base = priv->base_vid[hw_plane];
|
|
|
|
return readl(base + reg);
|
|
}
|
|
|
|
static void dss_ovr_write(struct tidss_drv_priv *priv, u32 hw_videoport,
|
|
u16 reg, u32 val)
|
|
{
|
|
void __iomem *base = priv->base_ovr[hw_videoport];
|
|
|
|
writel(val, base + reg);
|
|
}
|
|
|
|
static u32 dss_ovr_read(struct tidss_drv_priv *priv, u32 hw_videoport, u16 reg)
|
|
{
|
|
void __iomem *base = priv->base_ovr[hw_videoport];
|
|
|
|
return readl(base + reg);
|
|
}
|
|
|
|
static void dss_vp_write(struct tidss_drv_priv *priv, u32 hw_videoport,
|
|
u16 reg, u32 val)
|
|
{
|
|
void __iomem *base = priv->base_vp[hw_videoport];
|
|
|
|
writel(val, base + reg);
|
|
}
|
|
|
|
static u32 dss_vp_read(struct tidss_drv_priv *priv, u32 hw_videoport, u16 reg)
|
|
{
|
|
void __iomem *base = priv->base_vp[hw_videoport];
|
|
|
|
return readl(base + reg);
|
|
}
|
|
|
|
/* generate mask on a register */
|
|
static u32 FLD_MASK(u32 start, u32 end)
|
|
{
|
|
return ((1 << (start - end + 1)) - 1) << end;
|
|
}
|
|
|
|
/* set the given val in specified range */
|
|
static u32 FLD_VAL(u32 val, u32 start, u32 end)
|
|
{
|
|
return (val << end) & FLD_MASK(start, end);
|
|
}
|
|
|
|
/* return the value in the specified range */
|
|
static u32 FLD_GET(u32 val, u32 start, u32 end)
|
|
{
|
|
return (val & FLD_MASK(start, end)) >> end;
|
|
}
|
|
|
|
/* modify the value of the specified range */
|
|
static u32 FLD_MOD(u32 orig, u32 val, u32 start, u32 end)
|
|
{
|
|
return (orig & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end);
|
|
}
|
|
|
|
/* read and modify common register region of DSS*/
|
|
__maybe_unused
|
|
static u32 REG_GET(struct tidss_drv_priv *priv, u32 idx, u32 start, u32 end)
|
|
{
|
|
return FLD_GET(dss_read(priv, idx), start, end);
|
|
}
|
|
|
|
static void REG_FLD_MOD(struct tidss_drv_priv *priv, u32 idx, u32 val,
|
|
u32 start, u32 end)
|
|
{
|
|
dss_write(priv, idx, FLD_MOD(dss_read(priv, idx), val,
|
|
start, end));
|
|
}
|
|
|
|
/* read and modify planes vid1 and vid2 register of DSS*/
|
|
static u32 VID_REG_GET(struct tidss_drv_priv *priv, u32 hw_plane, u32 idx,
|
|
u32 start, u32 end)
|
|
{
|
|
return FLD_GET(dss_vid_read(priv, hw_plane, idx), start, end);
|
|
}
|
|
|
|
static void VID_REG_FLD_MOD(struct tidss_drv_priv *priv, u32 hw_plane, u32 idx,
|
|
u32 val, u32 start, u32 end)
|
|
{
|
|
dss_vid_write(priv, hw_plane, idx,
|
|
FLD_MOD(dss_vid_read(priv, hw_plane, idx),
|
|
val, start, end));
|
|
}
|
|
|
|
/* read and modify port vid1 and vid2 registers of DSS*/
|
|
__maybe_unused
|
|
static u32 VP_REG_GET(struct tidss_drv_priv *priv, u32 vp, u32 idx,
|
|
u32 start, u32 end)
|
|
{
|
|
return FLD_GET(dss_vp_read(priv, vp, idx), start, end);
|
|
}
|
|
|
|
static void VP_REG_FLD_MOD(struct tidss_drv_priv *priv, u32 vp, u32 idx, u32 val,
|
|
u32 start, u32 end)
|
|
{
|
|
dss_vp_write(priv, vp, idx, FLD_MOD(dss_vp_read(priv, vp, idx),
|
|
val, start, end));
|
|
}
|
|
|
|
/* read and modify overlay ovr1 and ovr2 registers of DSS*/
|
|
__maybe_unused
|
|
static u32 OVR_REG_GET(struct tidss_drv_priv *priv, u32 ovr, u32 idx,
|
|
u32 start, u32 end)
|
|
{
|
|
return FLD_GET(dss_ovr_read(priv, ovr, idx), start, end);
|
|
}
|
|
|
|
static void OVR_REG_FLD_MOD(struct tidss_drv_priv *priv, u32 ovr, u32 idx,
|
|
u32 val, u32 start, u32 end)
|
|
{
|
|
dss_ovr_write(priv, ovr, idx, FLD_MOD(dss_ovr_read(priv, ovr, idx),
|
|
val, start, end));
|
|
}
|
|
|
|
static void dss_oldi_tx_power(struct tidss_drv_priv *priv, bool power)
|
|
{
|
|
u32 val;
|
|
|
|
if (WARN_ON(!priv->oldi_io_ctrl))
|
|
return;
|
|
|
|
if (priv->feat->subrev == DSS_AM625) {
|
|
if (power) {
|
|
switch (priv->oldi_mode) {
|
|
case OLDI_SINGLE_LINK_SINGLE_MODE:
|
|
/* Power down OLDI TX 1 */
|
|
val = OLDI1_PWRDN_TX;
|
|
break;
|
|
case OLDI_DUAL_LINK:
|
|
/* No Power down */
|
|
val = 0;
|
|
break;
|
|
default:
|
|
/* Power down both the OLDI TXes */
|
|
val = OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX;
|
|
break;
|
|
}
|
|
} else {
|
|
val = OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX;
|
|
}
|
|
regmap_update_bits(priv->oldi_io_ctrl, OLDI_PD_CTRL,
|
|
OLDI_BANDGAP_PWR | OLDI0_PWRDN_TX | OLDI1_PWRDN_TX, val);
|
|
}
|
|
}
|
|
|
|
static void dss_set_num_datalines(struct tidss_drv_priv *priv,
|
|
u32 hw_videoport)
|
|
{
|
|
int v;
|
|
u32 num_lines = priv->bus_format->data_width;
|
|
|
|
switch (num_lines) {
|
|
case 12:
|
|
v = 0; break;
|
|
case 16:
|
|
v = 1; break;
|
|
case 18:
|
|
v = 2; break;
|
|
case 24:
|
|
v = 3; break;
|
|
case 30:
|
|
v = 4; break;
|
|
case 36:
|
|
v = 5; break;
|
|
default:
|
|
WARN_ON(1);
|
|
v = 3;
|
|
}
|
|
|
|
VP_REG_FLD_MOD(priv, hw_videoport, DSS_VP_CONTROL, v, 10, 8);
|
|
}
|
|
|
|
static void dss_enable_oldi(struct tidss_drv_priv *priv, u32 hw_videoport)
|
|
{
|
|
u32 oldi_cfg = 0;
|
|
u32 oldi_reset_bit = BIT(5 + hw_videoport);
|
|
int count = 0;
|
|
|
|
/*
|
|
* For the moment MASTERSLAVE, and SRC bits of DSS_VP_DSS_OLDI_CFG are
|
|
* set statically to 0.
|
|
*/
|
|
|
|
if (priv->bus_format->data_width == 24)
|
|
oldi_cfg |= BIT(8); /* MSB */
|
|
else if (priv->bus_format->data_width != 18)
|
|
dev_warn(priv->dev, "%s: %d port width not supported\n",
|
|
__func__, priv->bus_format->data_width);
|
|
|
|
oldi_cfg |= BIT(7); /* DEPOL */
|
|
|
|
oldi_cfg = FLD_MOD(oldi_cfg, priv->bus_format->oldi_mode_reg_val, 3, 1);
|
|
|
|
oldi_cfg |= BIT(12); /* SOFTRST */
|
|
|
|
oldi_cfg |= BIT(0); /* ENABLE */
|
|
|
|
switch (priv->oldi_mode) {
|
|
case OLDI_MODE_OFF:
|
|
oldi_cfg &= ~BIT(0); /* DISABLE */
|
|
break;
|
|
|
|
case OLDI_SINGLE_LINK_SINGLE_MODE:
|
|
/* All configuration is done for this mode. */
|
|
break;
|
|
|
|
case OLDI_SINGLE_LINK_DUPLICATE_MODE:
|
|
oldi_cfg |= BIT(5); /* DUPLICATE MODE */
|
|
break;
|
|
|
|
case OLDI_DUAL_LINK:
|
|
oldi_cfg |= BIT(11); /* DUALMODESYNC */
|
|
oldi_cfg |= BIT(3); /* data-mapping field also indicates dual-link mode */
|
|
break;
|
|
|
|
default:
|
|
dev_warn(priv->dev, "%s: Incorrect oldi mode. Returning.\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_DSS_OLDI_CFG, oldi_cfg);
|
|
|
|
while (!(oldi_reset_bit & dss_read(priv, DSS_SYSSTATUS)) &&
|
|
count < 10000)
|
|
count++;
|
|
|
|
if (!(oldi_reset_bit & dss_read(priv, DSS_SYSSTATUS)))
|
|
dev_warn(priv->dev, "%s: timeout waiting OLDI reset done\n",
|
|
__func__);
|
|
}
|
|
|
|
static const struct dss_color_lut dss_vp_gamma_default_lut[] = {
|
|
{ .red = 0, .green = 0, .blue = 0, },
|
|
{ .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, },
|
|
};
|
|
|
|
static void dss_vp_write_gamma_table(struct tidss_drv_priv *priv,
|
|
u32 hw_videoport)
|
|
{
|
|
u32 *table = priv->vp_data[hw_videoport].gamma_table;
|
|
u32 hwlen = priv->feat->vp_feat.color.gamma_size;
|
|
unsigned int i;
|
|
|
|
dev_dbg(priv->dev, "%s: hw_videoport %d\n", __func__, hw_videoport);
|
|
|
|
for (i = 0; i < hwlen; ++i) {
|
|
u32 v = table[i];
|
|
|
|
v |= i << 24;
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_GAMMA_TABLE, v);
|
|
}
|
|
}
|
|
|
|
static void dss_vp_set_gamma(struct tidss_drv_priv *priv,
|
|
u32 hw_videoport, const struct dss_color_lut *lut,
|
|
unsigned int length)
|
|
{
|
|
u32 *table = priv->vp_data[hw_videoport].gamma_table;
|
|
u32 hwlen = priv->feat->vp_feat.color.gamma_size;
|
|
u32 hwbits;
|
|
unsigned int i;
|
|
|
|
dev_dbg(priv->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n",
|
|
__func__, hw_videoport, length, hwlen);
|
|
|
|
if (priv->feat->vp_feat.color.gamma_type == TIDSS_GAMMA_10BIT)
|
|
hwbits = 10;
|
|
else
|
|
hwbits = 8;
|
|
|
|
lut = dss_vp_gamma_default_lut;
|
|
length = ARRAY_SIZE(dss_vp_gamma_default_lut);
|
|
|
|
for (i = 0; i < length - 1; ++i) {
|
|
unsigned int first = i * (hwlen - 1) / (length - 1);
|
|
unsigned int last = (i + 1) * (hwlen - 1) / (length - 1);
|
|
unsigned int w = last - first;
|
|
u16 r, g, b;
|
|
unsigned int j;
|
|
|
|
if (w == 0)
|
|
continue;
|
|
|
|
for (j = 0; j <= w; j++) {
|
|
r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w;
|
|
g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w;
|
|
b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w;
|
|
|
|
r >>= 16 - hwbits;
|
|
g >>= 16 - hwbits;
|
|
b >>= 16 - hwbits;
|
|
|
|
table[first + j] = (r << (hwbits * 2)) |
|
|
(g << hwbits) | b;
|
|
}
|
|
}
|
|
|
|
dss_vp_write_gamma_table(priv, hw_videoport);
|
|
}
|
|
|
|
void dss_vp_enable(struct tidss_drv_priv *priv, u32 hw_videoport, struct display_timing *timing)
|
|
{
|
|
bool align, onoff, rf, ieo, ipc, ihs, ivs;
|
|
u32 hsw, hfp, hbp, vsw, vfp, vbp;
|
|
|
|
dss_set_num_datalines(priv, hw_videoport);
|
|
|
|
/* panel parameters to set clocks for video port*/
|
|
hfp = timing->hfront_porch.typ;
|
|
hsw = timing->hsync_len.typ;
|
|
hbp = timing->hback_porch.typ;
|
|
vfp = timing->vfront_porch.typ;
|
|
vsw = timing->vsync_len.typ;
|
|
vbp = timing->vback_porch.typ;
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_TIMING_H,
|
|
FLD_VAL(hsw - 1, 7, 0) |
|
|
FLD_VAL(hfp - 1, 19, 8) | FLD_VAL(hbp - 1, 31, 20));
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_TIMING_V,
|
|
FLD_VAL(vsw - 1, 7, 0) |
|
|
FLD_VAL(vfp, 19, 8) | FLD_VAL(vbp, 31, 20));
|
|
|
|
ivs = !!(timing->flags & (1 << 3));
|
|
|
|
ihs = !!(timing->flags & (1 << 1));
|
|
|
|
ieo = 0;
|
|
|
|
ipc = 0;
|
|
|
|
/* always use the 'rf' setting */
|
|
onoff = true;
|
|
|
|
rf = true;
|
|
|
|
/* always use aligned syncs */
|
|
align = true;
|
|
|
|
/* always use DE_HIGH for OLDI */
|
|
if (priv->feat->vp_bus_type[hw_videoport] == DSS_VP_OLDI)
|
|
ieo = false;
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_POL_FREQ,
|
|
FLD_VAL(align, 18, 18) |
|
|
FLD_VAL(onoff, 17, 17) |
|
|
FLD_VAL(rf, 16, 16) |
|
|
FLD_VAL(ieo, 15, 15) |
|
|
FLD_VAL(ipc, 14, 14) |
|
|
FLD_VAL(ihs, 13, 13) |
|
|
FLD_VAL(ivs, 12, 12));
|
|
|
|
dss_vp_write(priv, hw_videoport, DSS_VP_SIZE_SCREEN,
|
|
FLD_VAL(timing->hactive.typ - 1, 11, 0) |
|
|
FLD_VAL(timing->vactive.typ - 1, 27, 16));
|
|
|
|
VP_REG_FLD_MOD(priv, hw_videoport, DSS_VP_CONTROL, 1, 0, 0);
|
|
}
|
|
|
|
enum c8_to_c12_mode { C8_TO_C12_REPLICATE, C8_TO_C12_MAX, C8_TO_C12_MIN };
|
|
|
|
static u16 c8_to_c12(u8 c8, enum c8_to_c12_mode mode)
|
|
{
|
|
u16 c12;
|
|
|
|
c12 = c8 << 4;
|
|
|
|
switch (mode) {
|
|
case C8_TO_C12_REPLICATE:
|
|
/* Copy c8 4 MSB to 4 LSB for full scale c12 */
|
|
c12 |= c8 >> 4;
|
|
break;
|
|
case C8_TO_C12_MAX:
|
|
c12 |= 0xF;
|
|
break;
|
|
default:
|
|
case C8_TO_C12_MIN:
|
|
break;
|
|
}
|
|
|
|
return c12;
|
|
}
|
|
|
|
static u64 argb8888_to_argb12121212(u32 argb8888, enum c8_to_c12_mode m)
|
|
{
|
|
u8 a, r, g, b;
|
|
u64 v;
|
|
|
|
a = (argb8888 >> 24) & 0xff;
|
|
r = (argb8888 >> 16) & 0xff;
|
|
g = (argb8888 >> 8) & 0xff;
|
|
b = (argb8888 >> 0) & 0xff;
|
|
|
|
v = ((u64)c8_to_c12(a, m) << 36) | ((u64)c8_to_c12(r, m) << 24) |
|
|
((u64)c8_to_c12(g, m) << 12) | (u64)c8_to_c12(b, m);
|
|
|
|
return v;
|
|
}
|
|
|
|
static void dss_vp_set_default_color(struct tidss_drv_priv *priv,
|
|
u32 hw_videoport, u32 default_color)
|
|
{
|
|
u64 v;
|
|
|
|
v = argb8888_to_argb12121212(default_color, C8_TO_C12_REPLICATE);
|
|
dss_ovr_write(priv, hw_videoport,
|
|
DSS_OVR_DEFAULT_COLOR, v & 0xffffffff);
|
|
dss_ovr_write(priv, hw_videoport,
|
|
DSS_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff);
|
|
}
|
|
|
|
int dss_vp_enable_clk(struct tidss_drv_priv *priv, u32 hw_videoport)
|
|
{
|
|
int ret = clk_prepare_enable(&priv->vp_clk[hw_videoport]);
|
|
|
|
if (ret)
|
|
dev_dbg(priv->dev, "%s: enabling clk failed: %d\n", __func__,
|
|
ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void dss_vp_prepare(struct tidss_drv_priv *priv, u32 hw_videoport)
|
|
{
|
|
dss_vp_set_gamma(priv, hw_videoport, NULL, 0);
|
|
dss_vp_set_default_color(priv, 0, 0);
|
|
|
|
if (priv->feat->vp_bus_type[hw_videoport] == DSS_VP_OLDI) {
|
|
dss_oldi_tx_power(priv, true);
|
|
dss_enable_oldi(priv, hw_videoport);
|
|
}
|
|
}
|
|
|
|
static
|
|
unsigned int dss_pclk_diff(unsigned long rate, unsigned long real_rate)
|
|
{
|
|
int r = rate / 100, rr = real_rate / 100;
|
|
|
|
return (unsigned int)(abs(((rr - r) * 100) / r));
|
|
}
|
|
|
|
int dss_vp_set_clk_rate(struct tidss_drv_priv *priv, u32 hw_videoport,
|
|
unsigned long rate)
|
|
{
|
|
int r;
|
|
unsigned long new_rate;
|
|
|
|
/*
|
|
* For AM625 OLDI video ports, the requested pixel clock needs to take into account the
|
|
* serial clock required for the serialization of DPI signals into LVDS signals. The
|
|
* incoming pixel clock on the OLDI video port gets divided by 7 whenever OLDI enable bit
|
|
* gets set.
|
|
*/
|
|
if (priv->feat->vp_bus_type[hw_videoport] == DSS_VP_OLDI &&
|
|
priv->feat->subrev == DSS_AM625)
|
|
rate *= 7;
|
|
|
|
r = clk_set_rate(&priv->vp_clk[hw_videoport], rate);
|
|
|
|
new_rate = clk_get_rate(&priv->vp_clk[hw_videoport]);
|
|
|
|
if (dss_pclk_diff(rate, new_rate) > 5)
|
|
dev_warn(priv->dev,
|
|
"vp%d: Clock rate %lu differs over 5%% from requested %lu\n",
|
|
hw_videoport, new_rate, rate);
|
|
|
|
dev_dbg(priv->dev, "vp%d: new rate %lu Hz (requested %lu Hz)\n",
|
|
hw_videoport, clk_get_rate(&priv->vp_clk[hw_videoport]), rate);
|
|
return 0;
|
|
}
|
|
|
|
static void dss_ovr_set_plane(struct tidss_drv_priv *priv,
|
|
u32 hw_plane, u32 hw_ovr,
|
|
u32 x, u32 y, u32 layer)
|
|
{
|
|
OVR_REG_FLD_MOD(priv, hw_ovr, DSS_OVR_ATTRIBUTES(layer),
|
|
0x1, 4, 1);
|
|
OVR_REG_FLD_MOD(priv, hw_ovr, DSS_OVR_ATTRIBUTES(layer),
|
|
x, 17, 6);
|
|
OVR_REG_FLD_MOD(priv, hw_ovr, DSS_OVR_ATTRIBUTES(layer),
|
|
y, 30, 19);
|
|
}
|
|
|
|
void dss_ovr_enable_layer(struct tidss_drv_priv *priv,
|
|
u32 hw_ovr, u32 layer, bool enable)
|
|
{
|
|
OVR_REG_FLD_MOD(priv, hw_ovr, DSS_OVR_ATTRIBUTES(layer),
|
|
!!enable, 0, 0);
|
|
}
|
|
|
|
static void dss_vid_csc_enable(struct tidss_drv_priv *priv, u32 hw_plane,
|
|
bool enable)
|
|
{
|
|
VID_REG_FLD_MOD(priv, hw_plane, DSS_VID_ATTRIBUTES, !!enable, 9, 9);
|
|
}
|
|
|
|
int dss_plane_setup(struct tidss_drv_priv *priv, u32 hw_plane, u32 hw_videoport)
|
|
{
|
|
VID_REG_FLD_MOD(priv, hw_plane, DSS_VID_ATTRIBUTES,
|
|
priv->pixel_format, 6, 1);
|
|
|
|
dss_vid_write(priv, hw_plane, DSS_VID_PICTURE_SIZE,
|
|
((LCD_MAX_WIDTH - 1) | ((LCD_MAX_HEIGHT - 1) << 16)));
|
|
|
|
dss_vid_csc_enable(priv, hw_plane, false);
|
|
|
|
dss_vid_write(priv, hw_plane, DSS_VID_GLOBAL_ALPHA, 0xFF);
|
|
|
|
VID_REG_FLD_MOD(priv, hw_plane, DSS_VID_ATTRIBUTES, 1, 28, 28);
|
|
return 0;
|
|
}
|
|
|
|
int dss_plane_enable(struct tidss_drv_priv *priv, u32 hw_plane, bool enable)
|
|
{
|
|
VID_REG_FLD_MOD(priv, hw_plane, DSS_VID_ATTRIBUTES, !!enable, 0, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u32 dss_vid_get_fifo_size(struct tidss_drv_priv *priv, u32 hw_plane)
|
|
{
|
|
return VID_REG_GET(priv, hw_plane, DSS_VID_BUF_SIZE_STATUS, 15, 0);
|
|
}
|
|
|
|
static void dss_vid_set_mflag_threshold(struct tidss_drv_priv *priv,
|
|
u32 hw_plane, u32 low, u32 high)
|
|
{
|
|
dss_vid_write(priv, hw_plane, DSS_VID_MFLAG_THRESHOLD,
|
|
FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
|
|
}
|
|
|
|
static
|
|
void dss_vid_set_buf_threshold(struct tidss_drv_priv *priv,
|
|
u32 hw_plane, u32 low, u32 high)
|
|
{
|
|
dss_vid_write(priv, hw_plane, DSS_VID_BUF_THRESHOLD,
|
|
FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
|
|
}
|
|
|
|
static void dss_plane_init(struct tidss_drv_priv *priv)
|
|
{
|
|
unsigned int hw_plane;
|
|
u32 cba_lo_pri = 1;
|
|
u32 cba_hi_pri = 0;
|
|
|
|
REG_FLD_MOD(priv, DSS_CBA_CFG, cba_lo_pri, 2, 0);
|
|
REG_FLD_MOD(priv, DSS_CBA_CFG, cba_hi_pri, 5, 3);
|
|
|
|
/* MFLAG_CTRL = ENABLED */
|
|
REG_FLD_MOD(priv, DSS_GLOBAL_MFLAG_ATTRIBUTE, 2, 1, 0);
|
|
/* MFLAG_START = MFLAGNORMALSTARTMODE */
|
|
REG_FLD_MOD(priv, DSS_GLOBAL_MFLAG_ATTRIBUTE, 0, 6, 6);
|
|
|
|
for (hw_plane = 0; hw_plane < priv->feat->num_planes; hw_plane++) {
|
|
u32 size = dss_vid_get_fifo_size(priv, hw_plane);
|
|
u32 thr_low, thr_high;
|
|
u32 mflag_low, mflag_high;
|
|
u32 preload;
|
|
|
|
thr_high = size - 1;
|
|
thr_low = size / 2;
|
|
|
|
mflag_high = size * 2 / 3;
|
|
mflag_low = size / 3;
|
|
|
|
preload = thr_low;
|
|
|
|
dev_dbg(priv->dev,
|
|
"%s: bufsize %u, buf_threshold %u/%u, mflag threshold %u/%u preload %u\n",
|
|
priv->feat->vid_name[hw_plane],
|
|
size,
|
|
thr_high, thr_low,
|
|
mflag_high, mflag_low,
|
|
preload);
|
|
|
|
dss_vid_set_buf_threshold(priv, hw_plane,
|
|
thr_low, thr_high);
|
|
dss_vid_set_mflag_threshold(priv, hw_plane,
|
|
mflag_low, mflag_high);
|
|
|
|
dss_vid_write(priv, hw_plane, DSS_VID_PRELOAD, preload);
|
|
|
|
/* Prefech up to PRELOAD value */
|
|
VID_REG_FLD_MOD(priv, hw_plane, DSS_VID_ATTRIBUTES, 0,
|
|
19, 19);
|
|
}
|
|
}
|
|
|
|
static void dss_vp_init(struct tidss_drv_priv *priv)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* Enable the gamma Shadow bit-field for all VPs*/
|
|
for (i = 0; i < priv->feat->num_vps; i++)
|
|
VP_REG_FLD_MOD(priv, i, DSS_VP_CONFIG, 1, 2, 2);
|
|
}
|
|
|
|
static int dss_init_am65x_oldi_io_ctrl(struct udevice *dev,
|
|
struct tidss_drv_priv *priv)
|
|
{
|
|
struct udevice *syscon;
|
|
struct regmap *regmap;
|
|
int ret = 0;
|
|
|
|
ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "ti,am65x-oldi-io-ctrl",
|
|
&syscon);
|
|
if (ret) {
|
|
debug("unable to find ti,am65x-oldi-io-ctrl syscon device (%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* get grf-reg base address */
|
|
regmap = syscon_get_regmap(syscon);
|
|
if (!regmap) {
|
|
debug("unable to find rockchip grf regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
priv->oldi_io_ctrl = regmap;
|
|
return 0;
|
|
}
|
|
|
|
static int tidss_drv_probe(struct udevice *dev)
|
|
{
|
|
struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev);
|
|
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
struct tidss_drv_priv *priv = dev_get_priv(dev);
|
|
struct udevice *panel = NULL;
|
|
struct display_timing timings;
|
|
unsigned int i;
|
|
int ret = 0;
|
|
const char *mode;
|
|
|
|
priv->dev = dev;
|
|
|
|
priv->feat = &dss_am625_feats;
|
|
|
|
/*
|
|
* set your plane format based on your bmp image
|
|
* Supported 24bpp and 32bpp bmpimages
|
|
*/
|
|
|
|
priv->pixel_format = DSS_FORMAT_XRGB8888;
|
|
|
|
dss_common_regmap = priv->feat->common_regs;
|
|
|
|
ret = uclass_first_device_err(UCLASS_PANEL, &panel);
|
|
if (ret) {
|
|
if (ret != -ENODEV)
|
|
dev_err(dev, "panel device error %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = panel_get_display_timing(panel, &timings);
|
|
if (ret) {
|
|
ret = ofnode_decode_panel_timing(dev_ofnode(panel),
|
|
&timings);
|
|
if (ret) {
|
|
dev_err(dev, "decode display timing error %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
mode = ofnode_read_string(dev_ofnode(panel), "data-mapping");
|
|
if (!mode) {
|
|
debug("%s: Could not read mode property\n", dev->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
uc_priv->bpix = VIDEO_BPP32;
|
|
|
|
if (!strcmp(mode, "vesa-24"))
|
|
priv->bus_format = &dss_bus_formats[7];
|
|
else
|
|
priv->bus_format = &dss_bus_formats[8];
|
|
|
|
/* Common address */
|
|
priv->base_common = dev_remap_addr_index(dev, 0);
|
|
if (!priv->base_common)
|
|
return -EINVAL;
|
|
|
|
/* plane address setup and enable */
|
|
for (i = 0; i < priv->feat->num_planes; i++) {
|
|
priv->base_vid[i] = dev_remap_addr_index(dev, i + 2);
|
|
if (!priv->base_vid[i])
|
|
return -EINVAL;
|
|
}
|
|
|
|
dss_vid_write(priv, 0, DSS_VID_BA_0, uc_plat->base & 0xffffffff);
|
|
dss_vid_write(priv, 0, DSS_VID_BA_EXT_0, (u64)uc_plat->base >> 32);
|
|
dss_vid_write(priv, 0, DSS_VID_BA_1, uc_plat->base & 0xffffffff);
|
|
dss_vid_write(priv, 0, DSS_VID_BA_EXT_1, (u64)uc_plat->base >> 32);
|
|
|
|
ret = dss_plane_setup(priv, 0, 0);
|
|
if (ret) {
|
|
dss_plane_enable(priv, 0, false);
|
|
return ret;
|
|
}
|
|
|
|
dss_plane_enable(priv, 0, true);
|
|
dss_plane_init(priv);
|
|
|
|
/* video port address clocks and enable */
|
|
for (i = 0; i < priv->feat->num_vps; i++) {
|
|
priv->base_ovr[i] = dev_remap_addr_index(dev, i + 4);
|
|
priv->base_vp[i] = dev_remap_addr_index(dev, i + 6);
|
|
}
|
|
|
|
ret = clk_get_by_name(dev, "vp1", &priv->vp_clk[0]);
|
|
if (ret) {
|
|
dev_err(dev, "video port %d clock enable error %d\n", i, ret);
|
|
return ret;
|
|
}
|
|
|
|
dss_ovr_set_plane(priv, 1, 0, 0, 0, 0);
|
|
dss_ovr_enable_layer(priv, 0, 0, true);
|
|
|
|
/* Video Port cloks */
|
|
dss_vp_enable_clk(priv, 0);
|
|
|
|
dss_vp_set_clk_rate(priv, 0, timings.pixelclock.typ * 1000);
|
|
|
|
priv->oldi_mode = OLDI_MODE_OFF;
|
|
uc_priv->xsize = timings.hactive.typ;
|
|
uc_priv->ysize = timings.vactive.typ;
|
|
if (priv->feat->subrev == DSS_AM65X || priv->feat->subrev == DSS_AM625) {
|
|
priv->oldi_mode = OLDI_DUAL_LINK;
|
|
if (priv->oldi_mode) {
|
|
ret = dss_init_am65x_oldi_io_ctrl(dev, priv);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
dss_vp_prepare(priv, 0);
|
|
dss_vp_enable(priv, 0, &timings);
|
|
dss_vp_init(priv);
|
|
|
|
ret = clk_get_by_name(dev, "fck", &priv->fclk);
|
|
if (ret) {
|
|
dev_err(dev, "peripheral clock get error %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_enable(&priv->fclk);
|
|
if (ret) {
|
|
dev_err(dev, "peripheral clock enable error %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (IS_ERR(&priv->fclk)) {
|
|
dev_err(dev, "%s: Failed to get fclk: %ld\n",
|
|
__func__, PTR_ERR(&priv->fclk));
|
|
return PTR_ERR(&priv->fclk);
|
|
}
|
|
|
|
dev_dbg(dev, "DSS fclk %lu Hz\n", clk_get_rate(&priv->fclk));
|
|
|
|
video_set_flush_dcache(dev, true);
|
|
return 0;
|
|
}
|
|
|
|
static int tidss_drv_remove(struct udevice *dev)
|
|
{
|
|
if (CONFIG_IS_ENABLED(VIDEO_REMOVE)) {
|
|
struct tidss_drv_priv *priv = dev_get_priv(dev);
|
|
|
|
VP_REG_FLD_MOD(priv, 0, DSS_VP_CONTROL, 0, 0, 0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tidss_drv_bind(struct udevice *dev)
|
|
{
|
|
struct video_uc_plat *uc_plat = dev_get_uclass_plat(dev);
|
|
|
|
uc_plat->size = ((LCD_MAX_WIDTH * LCD_MAX_HEIGHT *
|
|
(1 << LCD_MAX_LOG2_BPP)) >> 3) + 0x20;
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id tidss_drv_ids[] = {
|
|
{ .compatible = "ti,am625-dss" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(tidss_drv) = {
|
|
.name = "tidss_drv",
|
|
.id = UCLASS_VIDEO,
|
|
.of_match = tidss_drv_ids,
|
|
.bind = tidss_drv_bind,
|
|
.probe = tidss_drv_probe,
|
|
.remove = tidss_drv_remove,
|
|
.priv_auto = sizeof(struct tidss_drv_priv),
|
|
#if CONFIG_IS_ENABLED(VIDEO_REMOVE)
|
|
.flags = DM_FLAG_OS_PREPARE,
|
|
#else
|
|
.flags = DM_FLAG_OS_PREPARE | DM_FLAG_LEAVE_PD_ON,
|
|
#endif
|
|
};
|