2026-01-29 22:25:33 +08:00

515 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Phytium display port DRM driver
*
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
*/
#include "phytium_display_drv.h"
#include "pe220x_reg.h"
#include "phytium_dp.h"
#include "pe220x_dp.h"
static uint8_t pe220x_dp_source_lane_count[2] = {1, 1};
/* [reg][ling_rate 1.62->8.1] */
static int vco_val[12][4] = {
{0x0509, 0x0509, 0x0509, 0x0509}, /* CP_PADJ */
{0x0f00, 0x0f00, 0x0f00, 0x0f00}, /* CP_IADJ */
{0x0F08, 0x0F08, 0x0F08, 0x0F08}, /* FILT_PADJ */
{0x0061, 0x006C, 0x006C, 0x0051}, /* INTDIV */
{0x3333, 0x0000, 0x0000, 0x0000}, /* FRACDIVL */
{0x0000, 0x0000, 0x0000, 0x0000}, /* FRACDIVH */
{0x0042, 0x0048, 0x0048, 0x0036}, /* HIGH_THR */
{0x0002, 0x0002, 0x0002, 0x0002}, /* PDIAG_CTRL */
{0x0c5e, 0x0c5e, 0x0c5e, 0x0c5e}, /* VCOCAL_PLLCNT_START */
{0x00c7, 0x00c7, 0x00c7, 0x00c7}, /* LOCK_PEFCNT */
{0x00c7, 0x00c7, 0x00c7, 0x00c7}, /* LOCK_PLLCNT_START */
{0x0005, 0x0005, 0x0005, 0x0005}, /* LOCK_PLLCNT_THR */
};
/* [link_rate][swing][emphasis] */
static int mgnfs_val[4][4][4] = {
/* 1.62Gbps */
{
{0x0026, 0x001f, 0x0012, 0x0000},
{0x0013, 0x0013, 0x0000, 0x0000},
{0x0006, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 2.7Gbps */
{
{0x0026, 0x001f, 0x0012, 0x0000},
{0x0013, 0x0013, 0x0000, 0x0000},
{0x0006, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 5.4Gbps */
{
{0x001f, 0x0013, 0x005, 0x0000},
{0x0018, 0x006, 0x0000, 0x0000},
{0x000c, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 8.1Gbps */
{
{0x0026, 0x0013, 0x005, 0x0000},
{0x0013, 0x006, 0x0000, 0x0000},
{0x0006, 0x0000, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
};
/* [link_rate][swing][emphasis] */
static int cpost_val[4][4][4] = {
/* 1.62Gbps */
{
{0x0000, 0x0014, 0x0020, 0x002a},
{0x0000, 0x0010, 0x001f, 0x0000},
{0x0000, 0x0013, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 2.7Gbps */
{
{0x0000, 0x0014, 0x0020, 0x002a},
{0x0000, 0x0010, 0x001f, 0x0000},
{0x0000, 0x0013, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 5.4Gbps */
{
{0x0005, 0x0014, 0x0022, 0x002e},
{0x0000, 0x0013, 0x0020, 0x0000},
{0x0000, 0x0013, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
/* 8.1Gbps */
{
{0x0000, 0x0014, 0x0022, 0x002e},
{0x0000, 0x0013, 0x0020, 0x0000},
{0x0000, 0x0013, 0x0000, 0x0000},
{0x0000, 0x0000, 0x0000, 0x0000},
},
};
static int pe220x_dp_hw_set_phy_lane_and_rate(struct phytium_dp_device *phytium_dp,
uint8_t link_lane_count, uint32_t link_rate)
{
int port = phytium_dp->port%2;
int i = 0, data, tmp, tmp1, index = 0, mask = 0;
int timeout = 500, ret = 0;
/* set pma powerdown */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (A3_POWERDOWN3 << (i * A3_POWERDOWN3_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data);
/* lane pll disable */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++) {
data |= (PLL_EN << (i * PLL_EN_SHIFT));
mask |= (((1<<PLL_EN_SHIFT) - 1) << (i * PLL_EN_SHIFT));
}
data &= ~mask;
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_EN(port), data);
/* pma pll disable */
data = CONTROL_ENABLE & (~CONTROL_ENABLE_MASK);
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA_CONTROL(port), data);
/* read pma pll disable state */
mdelay(2);
phytium_phy_readl(phytium_dp, PE220X_PHY_PMA_CONTROL2(port));
/* config link rate */
switch (link_rate) {
case 810000:
tmp = PLL_LINK_RATE_810000;
tmp1 = HSCLK_LINK_RATE_810000;
index = 3;
break;
case 540000:
tmp = PLL_LINK_RATE_540000;
tmp1 = HSCLK_LINK_RATE_540000;
index = 2;
break;
case 270000:
tmp = PLL_LINK_RATE_270000;
tmp1 = HSCLK_LINK_RATE_270000;
index = 1;
break;
case 162000:
tmp = PLL_LINK_RATE_162000;
tmp1 = HSCLK_LINK_RATE_162000;
index = 0;
break;
default:
DRM_ERROR("phytium dp rate(%d) not support\n", link_rate);
tmp = PLL_LINK_RATE_162000;
tmp1 = HSCLK_LINK_RATE_162000;
index = 0;
break;
}
/* config analog pll for link0 */
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_CLK_SEL(port), tmp);
phytium_phy_writel(phytium_dp, PE220X_PHY_HSCLK0_SEL(port), HSCLK_LINK_0);
phytium_phy_writel(phytium_dp, PE220X_PHY_HSCLK0_DIV(port), tmp1);
/* config digital pll for link0 */
phytium_phy_writel(phytium_dp, PE220X_PHY_PLLDRC0_CTRL(port), PLLDRC_LINK0);
/* common for all rate */
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_DSM_M0(port), PLL0_DSM_M0);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_VCOCAL_START(port),
PLL0_VCOCAL_START);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_VCOCAL_CTRL(port),
PLL0_VCOCAL_CTRL);
/* different for all rate */
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_CP_PADJ(port),
vco_val[0][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_CP_IADJ(port),
vco_val[1][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_CP_FILT_PADJ(port),
vco_val[2][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_INTDIV(port),
vco_val[3][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_FRACDIVL(port),
vco_val[4][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_FRACDIVH(port),
vco_val[5][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_HIGH_THR(port),
vco_val[6][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_PDIAG_CTRL(port),
vco_val[7][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_VCOCAL_PLLCNT_START(port),
vco_val[8][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_LOCK_PEFCNT(port),
vco_val[9][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_LOCK_PLLCNT_START(port),
vco_val[10][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_LOCK_PLLCNT_THR(port),
vco_val[11][index]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_PSC_A0(port),
PLL0_TX_PSC_A0);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_PSC_A2(port),
PLL0_TX_PSC_A2);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_PSC_A3(port),
PLL0_TX_PSC_A3);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_PSC_A0(port),
PLL0_RX_PSC_A0);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_PSC_A2(port),
PLL0_RX_PSC_A2);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_PSC_A3(port),
PLL0_RX_PSC_A3);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_PSC_CAL(port),
PLL0_RX_PSC_CAL);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_XCVR_CTRL(port),
PLL0_XCVR_CTRL);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_GCSM1_CTRL(port),
PLL0_RX_GCSM1_CTRL);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_GCSM2_CTRL(port),
PLL0_RX_GCSM2_CTRL);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_RX_PERGCSM_CTRL(port),
PLL0_RX_PERGCSM_CTRL);
/* pma pll enable */
data = CONTROL_ENABLE;
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA_CONTROL(port), data);
/* lane pll enable */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (PLL_EN << (i * PLL_EN_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_EN(port), data);
/* set pma power active */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (A0_ACTIVE << (i * A0_ACTIVE_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data);
mask = PLL0_LOCK_DONE;
do {
mdelay(1);
timeout--;
tmp = phytium_phy_readl(phytium_dp, PE220X_PHY_PMA_CONTROL2(port));
} while ((!(tmp & mask)) && timeout);
if (timeout == 0) {
DRM_ERROR("dp(%d) phy pll lock failed\n", port);
ret = -1;
}
udelay(1);
return ret;
}
static void pe220x_dp_hw_set_phy_lane_setting(struct phytium_dp_device *phytium_dp,
uint32_t link_rate, uint8_t train_set)
{
int port = phytium_dp->port % 3;
int voltage_swing = 0;
int pre_emphasis = 0, link_rate_index = 0;
switch (train_set & DP_TRAIN_VOLTAGE_SWING_MASK) {
case DP_TRAIN_VOLTAGE_SWING_LEVEL_1:
voltage_swing = 1;
break;
case DP_TRAIN_VOLTAGE_SWING_LEVEL_2:
voltage_swing = 2;
break;
case DP_TRAIN_VOLTAGE_SWING_LEVEL_3:
voltage_swing = 3;
break;
default:
voltage_swing = 0;
break;
}
switch (train_set & DP_TRAIN_PRE_EMPHASIS_MASK) {
case DP_TRAIN_PRE_EMPH_LEVEL_1:
pre_emphasis = 1;
break;
case DP_TRAIN_PRE_EMPH_LEVEL_2:
pre_emphasis = 2;
break;
case DP_TRAIN_PRE_EMPH_LEVEL_3:
pre_emphasis = 3;
break;
default:
pre_emphasis = 0;
break;
}
switch (link_rate) {
case 810000:
link_rate_index = 3;
break;
case 540000:
link_rate_index = 2;
break;
case 270000:
link_rate_index = 1;
break;
case 162000:
link_rate_index = 0;
break;
default:
DRM_ERROR("phytium dp rate(%d) not support\n", link_rate);
link_rate_index = 2;
break;
}
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DIAG_ACYA(port), LOCK);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_TXCC_CTRL(port), TX_TXCC_CTRL);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DRV(port), TX_DRV);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_MGNFS(port),
mgnfs_val[link_rate_index][voltage_swing][pre_emphasis]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_CPOST(port),
cpost_val[link_rate_index][voltage_swing][pre_emphasis]);
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL0_TX_DIAG_ACYA(port), UNLOCK);
}
static int pe220x_dp_hw_init_phy(struct phytium_dp_device *phytium_dp)
{
int port = phytium_dp->port;
int i = 0, data, tmp, mask;
int timeout = 500, ret = 0;
phytium_phy_writel(phytium_dp, PE220X_PHY_APB_RESET(port), APB_RESET);
phytium_phy_writel(phytium_dp, PE220X_PHY_PIPE_RESET(port), RESET);
/* config lane to dp mode */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (LANE_BIT << (i * LANE_BIT_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_MODE(port), data);
/* pll clock enable */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (PLL_EN << (i * PLL_EN_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_EN(port), data);
/* config input 20 bit */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (BIT_20 << (i * BIT_20_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA_WIDTH(port), data);
/* config lane active power state */
data = 0;
for (i = 0; i < phytium_dp->source_max_lane_count; i++)
data |= (A0_ACTIVE << (i * A0_ACTIVE_SHIFT));
phytium_phy_writel(phytium_dp, PE220X_PHY_PMA0_POWER(port), data);
/* link reset */
phytium_phy_writel(phytium_dp, PE220X_PHY_LINK_RESET(port), LINK_RESET);
phytium_phy_writel(phytium_dp, PE220X_PHY_SGMII_DPSEL_INIT(port), DP_SEL);
/* config single link */
phytium_phy_writel(phytium_dp, PE220X_PHY_PLL_CFG(port), SINGLE_LINK);
/* pipe reset */
phytium_phy_writel(phytium_dp, PE220X_PHY_PIPE_RESET(port), RESET_DEASSERT);
mask = PLL0_LOCK_DONE;
do {
mdelay(1);
timeout--;
tmp = phytium_phy_readl(phytium_dp, PE220X_PHY_PMA_CONTROL2(port));
} while ((!(tmp & mask)) && timeout);
if (timeout == 0) {
DRM_ERROR("reset dp(%d) phy failed\n", port);
ret = -1;
}
udelay(1);
return ret;
}
static void pe220x_dp_hw_poweron_panel(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
int ret = 0;
phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_ENABLE,
0, PE220X_DC_CMD_REGISTER(port));
ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port),
FLAG_REQUEST, FLAG_REPLY);
if (ret < 0)
DRM_ERROR("%s: failed to poweron panel\n", __func__);
}
static void pe220x_dp_hw_poweroff_panel(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
int ret = 0;
phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | PANEL_POWER_DISABLE,
0, PE220X_DC_CMD_REGISTER(port));
ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port),
FLAG_REQUEST, FLAG_REPLY);
if (ret < 0)
DRM_ERROR("%s: failed to poweroff panel\n", __func__);
}
static void pe220x_dp_hw_enable_backlight(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port, ret = 0;
phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_ENABLE,
0, PE220X_DC_CMD_REGISTER(port));
ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port),
FLAG_REQUEST, FLAG_REPLY);
if (ret < 0)
DRM_ERROR("%s: failed to enable backlight\n", __func__);
}
static void pe220x_dp_hw_disable_backlight(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
int ret = 0;
phytium_writel_reg(priv, FLAG_REQUEST | CMD_BACKLIGHT | BACKLIGHT_DISABLE,
0, PE220X_DC_CMD_REGISTER(port));
ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port),
FLAG_REQUEST, FLAG_REPLY);
if (ret < 0)
DRM_ERROR("%s: failed to disable backlight\n", __func__);
}
static uint32_t pe220x_dp_hw_get_backlight(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int config;
uint32_t group_offset = priv->address_transform_base;
config = phytium_readl_reg(priv, group_offset, PE220X_DC_ADDRESS_TRANSFORM_BACKLIGHT_VALUE);
return ((config >> BACKLIGHT_VALUE_SHIFT) & BACKLIGHT_VALUE_MASK);
}
static int pe220x_dp_hw_set_backlight(struct phytium_dp_device *phytium_dp, uint32_t level)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
int config = 0;
int ret = 0;
if (level > PE220X_DP_BACKLIGHT_MAX) {
ret = -EINVAL;
goto out;
}
config = FLAG_REQUEST | CMD_BACKLIGHT | ((level & BACKLIGHT_MASK) << BACKLIGHT_SHIFT);
phytium_writel_reg(priv, config, 0, PE220X_DC_CMD_REGISTER(port));
ret = phytium_wait_cmd_done(priv, PE220X_DC_CMD_REGISTER(port),
FLAG_REQUEST, FLAG_REPLY);
if (ret < 0)
DRM_ERROR("%s: failed to set backlight\n", __func__);
out:
return ret;
}
bool pe220x_dp_hw_spread_is_enable(struct phytium_dp_device *phytium_dp)
{
return false;
}
int pe220x_dp_hw_reset(struct phytium_dp_device *phytium_dp)
{
struct drm_device *dev = phytium_dp->dev;
struct phytium_display_private *priv = dev->dev_private;
int port = phytium_dp->port;
uint32_t group_offset = priv->dp_reg_base[port];
phytium_writel_reg(priv, DP_RESET, group_offset, PE220X_DP_CONTROLLER_RESET);
udelay(500);
phytium_writel_reg(priv, AUX_CLK_DIVIDER_100, group_offset, PHYTIUM_DP_AUX_CLK_DIVIDER);
phytium_writel_reg(priv, SUPPORT_EDP_1_4, group_offset, PHYTIUM_EDP_CRC_ENABLE);
return 0;
}
uint8_t pe220x_dp_hw_get_source_lane_count(struct phytium_dp_device *phytium_dp)
{
return pe220x_dp_source_lane_count[phytium_dp->port];
}
static struct phytium_dp_func pe220x_dp_funcs = {
.dp_hw_get_source_lane_count = pe220x_dp_hw_get_source_lane_count,
.dp_hw_reset = pe220x_dp_hw_reset,
.dp_hw_spread_is_enable = pe220x_dp_hw_spread_is_enable,
.dp_hw_set_backlight = pe220x_dp_hw_set_backlight,
.dp_hw_get_backlight = pe220x_dp_hw_get_backlight,
.dp_hw_disable_backlight = pe220x_dp_hw_disable_backlight,
.dp_hw_enable_backlight = pe220x_dp_hw_enable_backlight,
.dp_hw_poweroff_panel = pe220x_dp_hw_poweroff_panel,
.dp_hw_poweron_panel = pe220x_dp_hw_poweron_panel,
.dp_hw_init_phy = pe220x_dp_hw_init_phy,
.dp_hw_set_phy_lane_setting = pe220x_dp_hw_set_phy_lane_setting,
.dp_hw_set_phy_lane_and_rate = pe220x_dp_hw_set_phy_lane_and_rate,
};
void pe220x_dp_func_register(struct phytium_dp_device *phytium_dp)
{
phytium_dp->funcs = &pe220x_dp_funcs;
}