Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/minmax.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "dp_comm.h"
#include "dp_reg.h"
#include "dp_hw.h"
#define HIBMC_AUX_CMD_REQ_LEN GENMASK(7, 4)
#define HIBMC_AUX_CMD_ADDR GENMASK(27, 8)
#define HIBMC_AUX_CMD_I2C_ADDR_ONLY BIT(28)
#define HIBMC_BYTES_IN_U32 4
#define HIBMC_AUX_I2C_WRITE_SUCCESS 0x1
#define HIBMC_DP_MIN_PULSE_NUM 0x9
#define BITS_IN_U8 8
static inline void hibmc_dp_aux_reset(struct hibmc_dp_dev *dp)
{
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x0);
usleep_range(10, 15);
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_RST_CTRL, HIBMC_DP_CFG_AUX_RST_N, 0x1);
}
static void hibmc_dp_aux_read_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size)
{
u32 reg_num;
u32 value;
u32 num;
u8 i, j;
reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32);
for (i = 0; i < reg_num; i++) {
/* number of bytes read from a single register */
num = min(size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32);
value = readl(dp->base + HIBMC_DP_AUX_RD_DATA0 + i * HIBMC_BYTES_IN_U32);
/* convert the 32-bit value of the register to the buffer. */
for (j = 0; j < num; j++)
buf[i * HIBMC_BYTES_IN_U32 + j] = value >> (j * BITS_IN_U8);
}
}
static void hibmc_dp_aux_write_data(struct hibmc_dp_dev *dp, u8 *buf, u8 size)
{
u32 reg_num;
u32 value;
u32 num;
u8 i, j;
reg_num = DIV_ROUND_UP(size, HIBMC_BYTES_IN_U32);
for (i = 0; i < reg_num; i++) {
/* number of bytes written to a single register */
num = min_t(u8, size - i * HIBMC_BYTES_IN_U32, HIBMC_BYTES_IN_U32);
value = 0;
/* obtain the 32-bit value written to a single register. */
for (j = 0; j < num; j++)
value |= buf[i * HIBMC_BYTES_IN_U32 + j] << (j * BITS_IN_U8);
/* writing data to a single register */
writel(value, dp->base + HIBMC_DP_AUX_WR_DATA0 + i * HIBMC_BYTES_IN_U32);
}
}
static u32 hibmc_dp_aux_build_cmd(const struct drm_dp_aux_msg *msg)
{
u32 aux_cmd = msg->request;
if (msg->size)
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_REQ_LEN, (msg->size - 1));
else
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_I2C_ADDR_ONLY, 1);
aux_cmd |= FIELD_PREP(HIBMC_AUX_CMD_ADDR, msg->address);
return aux_cmd;
}
/* ret >= 0, ret is size; ret < 0, ret is err code */
static int hibmc_dp_aux_parse_xfer(struct hibmc_dp_dev *dp, struct drm_dp_aux_msg *msg)
{
u32 buf_data_cnt;
u32 aux_status;
aux_status = readl(dp->base + HIBMC_DP_AUX_STATUS);
msg->reply = FIELD_GET(HIBMC_DP_CFG_AUX_STATUS, aux_status);
if (aux_status & HIBMC_DP_CFG_AUX_TIMEOUT)
return -ETIMEDOUT;
/* only address */
if (!msg->size)
return 0;
if (msg->reply != DP_AUX_NATIVE_REPLY_ACK)
return -EIO;
buf_data_cnt = FIELD_GET(HIBMC_DP_CFG_AUX_READY_DATA_BYTE, aux_status);
switch (msg->request) {
case DP_AUX_NATIVE_WRITE:
return msg->size;
case DP_AUX_I2C_WRITE | DP_AUX_I2C_MOT:
if (buf_data_cnt == HIBMC_AUX_I2C_WRITE_SUCCESS)
return msg->size;
else
return FIELD_GET(HIBMC_DP_CFG_AUX, aux_status);
case DP_AUX_NATIVE_READ:
case DP_AUX_I2C_READ | DP_AUX_I2C_MOT:
buf_data_cnt--;
if (buf_data_cnt != msg->size) {
/* only the successful part of data is read */
return -EBUSY;
}
/* all data is successfully read */
hibmc_dp_aux_read_data(dp, msg->buffer, msg->size);
return msg->size;
default:
return -EINVAL;
}
}
/* ret >= 0 ,ret is size; ret < 0, ret is err code */
static ssize_t hibmc_dp_aux_xfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg)
{
struct hibmc_dp *dp_priv = container_of(aux, struct hibmc_dp, aux);
struct hibmc_dp_dev *dp = dp_priv->dp_dev;
u32 aux_cmd;
int ret;
u32 val; /* val will be assigned at the beginning of readl_poll_timeout function */
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA0);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA1);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA2);
writel(0, dp->base + HIBMC_DP_AUX_WR_DATA3);
hibmc_dp_aux_write_data(dp, msg->buffer, msg->size);
aux_cmd = hibmc_dp_aux_build_cmd(msg);
writel(aux_cmd, dp->base + HIBMC_DP_AUX_CMD_ADDR);
/* enable aux transfer */
hibmc_dp_reg_write_field(dp, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_REQ, 0x1);
ret = readl_poll_timeout(dp->base + HIBMC_DP_AUX_REQ, val,
!(val & HIBMC_DP_CFG_AUX_REQ), 50, 5000);
if (ret) {
hibmc_dp_aux_reset(dp);
return ret;
}
return hibmc_dp_aux_parse_xfer(dp, msg);
}
void hibmc_dp_aux_init(struct hibmc_dp *dp)
{
hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0);
hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1);
hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM,
HIBMC_DP_MIN_PULSE_NUM);
dp->aux.transfer = hibmc_dp_aux_xfer;
dp->aux.name = "HIBMC DRM dp aux";
dp->aux.drm_dev = dp->drm_dev;
drm_dp_aux_init(&dp->aux);
dp->dp_dev->aux = &dp->aux;
}
@@ -0,0 +1,74 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_COMM_H
#define DP_COMM_H
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/kernel.h>
#include <linux/bitfield.h>
#include <linux/io.h>
#include <drm/display/drm_dp_helper.h>
#include "dp_hw.h"
#define HIBMC_DP_LANE_NUM_MAX 2
struct hibmc_link_status {
bool clock_recovered;
bool channel_equalized;
};
struct hibmc_link_cap {
u8 link_rate;
u8 lanes;
};
struct hibmc_dp_link {
struct hibmc_link_status status;
u8 train_set[HIBMC_DP_LANE_NUM_MAX];
struct hibmc_link_cap cap;
};
struct hibmc_dp_dev {
struct drm_dp_aux *aux;
struct drm_device *dev;
void __iomem *base;
struct mutex lock; /* protects concurrent RW in hibmc_dp_reg_write_field() */
struct hibmc_dp_link link;
u8 dpcd[DP_RECEIVER_CAP_SIZE];
u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS];
struct drm_dp_desc desc;
bool is_branch;
int hpd_status;
void __iomem *serdes_base;
};
#define dp_field_modify(reg_value, mask, val) \
do { \
(reg_value) &= ~(mask); \
(reg_value) |= FIELD_PREP(mask, val); \
} while (0) \
#define hibmc_dp_reg_write_field(dp, offset, mask, val) \
do { \
typeof(dp) _dp = dp; \
typeof(_dp->base) addr = (_dp->base + (offset)); \
mutex_lock(&_dp->lock); \
u32 reg_value = readl(addr); \
dp_field_modify(reg_value, mask, val); \
writel(reg_value, addr); \
mutex_unlock(&_dp->lock); \
} while (0)
void hibmc_dp_aux_init(struct hibmc_dp *dp);
int hibmc_dp_link_training(struct hibmc_dp_dev *dp);
int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp);
int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp);
int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX]);
void hibmc_dp_update_caps(struct hibmc_dp_dev *dp);
#endif
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_CONFIG_H
#define DP_CONFIG_H
#define HIBMC_DP_BPP 24
#define HIBMC_DP_SYMBOL_PER_FCLK 4
#define HIBMC_DP_MSA1 0x20
#define HIBMC_DP_MSA2 0x845c00
#define HIBMC_DP_OFFSET 0x1e0000
#define HIBMC_DP_HDCP 0x2
#define HIBMC_DP_INT_RST 0xffff
#define HIBMC_DP_DPTX_RST 0x3ff
#define HIBMC_DP_CLK_EN 0x7
#define HIBMC_DP_SYNC_EN_MASK 0x3
#define HIBMC_DP_LINK_RATE_CAL 27
#define HIBMC_DP_SYNC_DELAY(lanes) ((lanes) == 0x2 ? 86 : 46)
#define HIBMC_DP_INT_ENABLE 0xc
/* HIBMC_DP_LINK_RATE_CAL * 10000 * 80% = 216000 */
#define DP_MODE_VALI_CAL 216000
#endif
@@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/delay.h>
#include "dp_config.h"
#include "dp_comm.h"
#include "dp_reg.h"
#include "dp_hw.h"
static void hibmc_dp_set_tu(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 tu_symbol_frac_size;
u32 tu_symbol_size;
u32 rate_ks;
u8 lane_num;
u32 value;
u32 bpp;
lane_num = dp->link.cap.lanes;
if (lane_num == 0) {
drm_err(dp->dev, "set tu failed, lane num cannot be 0!\n");
return;
}
bpp = HIBMC_DP_BPP;
rate_ks = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL;
value = (mode->clock * bpp * 5) / (61 * lane_num * rate_ks);
if (value % 10 == 9) { /* 9 carry */
tu_symbol_size = value / 10 + 1;
tu_symbol_frac_size = 0;
} else {
tu_symbol_size = value / 10;
tu_symbol_frac_size = value % 10 + 1;
}
drm_dbg_dp(dp->dev, "tu value: %u.%u value: %u\n",
tu_symbol_size, tu_symbol_frac_size, value);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET,
HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE, tu_symbol_size);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET,
HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE, tu_symbol_frac_size);
}
static void hibmc_dp_set_sst(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 hblank_size;
u32 htotal_size;
u32 htotal_int;
u32 hblank_int;
u32 fclk; /* flink_clock */
fclk = dp->link.cap.link_rate * HIBMC_DP_LINK_RATE_CAL;
/* Considering the effect of spread spectrum, the value may be deviated.
* The coefficient (0.9947) is used to offset the deviation.
*/
htotal_int = mode->htotal * 9947 / 10000;
htotal_size = htotal_int * fclk / (HIBMC_DP_SYMBOL_PER_FCLK * (mode->clock / 1000));
hblank_int = mode->htotal - mode->hdisplay - mode->hdisplay * 53 / 10000;
hblank_size = hblank_int * fclk * 9947 /
(mode->clock * 10 * HIBMC_DP_SYMBOL_PER_FCLK);
drm_dbg_dp(dp->dev, "h_active %u v_active %u htotal_size %u hblank_size %u",
mode->hdisplay, mode->vdisplay, htotal_size, hblank_size);
drm_dbg_dp(dp->dev, "flink_clock %u pixel_clock %d", fclk, mode->clock / 1000);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE,
HIBMC_DP_CFG_STREAM_HTOTAL_SIZE, htotal_size);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_HORIZONTAL_SIZE,
HIBMC_DP_CFG_STREAM_HBLANK_SIZE, hblank_size);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_PACKET,
HIBMC_DP_CFG_STREAM_SYNC_CALIBRATION,
HIBMC_DP_SYNC_DELAY(dp->link.cap.lanes));
}
static void hibmc_dp_link_cfg(struct hibmc_dp_dev *dp, struct drm_display_mode *mode)
{
u32 timing_delay;
u32 vblank;
u32 hstart;
u32 vstart;
vblank = mode->vtotal - mode->vdisplay;
timing_delay = mode->htotal - mode->hsync_start;
hstart = mode->htotal - mode->hsync_start;
vstart = mode->vtotal - mode->vsync_start;
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0,
HIBMC_DP_CFG_TIMING_GEN0_HBLANK, mode->htotal - mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG0,
HIBMC_DP_CFG_TIMING_GEN0_HACTIVE, mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2,
HIBMC_DP_CFG_TIMING_GEN0_VBLANK, vblank);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG2,
HIBMC_DP_CFG_TIMING_GEN0_VACTIVE, mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_GEN_CONFIG3,
HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH,
mode->vsync_start - mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0,
HIBMC_DP_CFG_STREAM_HACTIVE, mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG0,
HIBMC_DP_CFG_STREAM_HBLANK, mode->htotal - mode->hdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG2,
HIBMC_DP_CFG_STREAM_HSYNC_WIDTH,
mode->hsync_end - mode->hsync_start);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1,
HIBMC_DP_CFG_STREAM_VACTIVE, mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG1,
HIBMC_DP_CFG_STREAM_VBLANK, vblank);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3,
HIBMC_DP_CFG_STREAM_VFRONT_PORCH,
mode->vsync_start - mode->vdisplay);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CONFIG3,
HIBMC_DP_CFG_STREAM_VSYNC_WIDTH,
mode->vsync_end - mode->vsync_start);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0,
HIBMC_DP_CFG_STREAM_VSTART, vstart);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_MSA0,
HIBMC_DP_CFG_STREAM_HSTART, hstart);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VSYNC_POLARITY,
mode->flags & DRM_MODE_FLAG_PVSYNC ? 1 : 0);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_HSYNC_POLARITY,
mode->flags & DRM_MODE_FLAG_PHSYNC ? 1 : 0);
/* MSA mic 0 and 1 */
writel(HIBMC_DP_MSA1, dp->base + HIBMC_DP_VIDEO_MSA1);
writel(HIBMC_DP_MSA2, dp->base + HIBMC_DP_VIDEO_MSA2);
hibmc_dp_set_tu(dp, mode);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_RGB_ENABLE, 0x1);
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_VIDEO_MAPPING, 0);
/* divide 2: up even */
if (timing_delay % 2)
timing_delay++;
hibmc_dp_reg_write_field(dp, HIBMC_DP_TIMING_MODEL_CTRL,
HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1, timing_delay);
hibmc_dp_set_sst(dp, mode);
}
int hibmc_dp_hw_init(struct hibmc_dp *dp)
{
struct drm_device *drm_dev = dp->drm_dev;
struct hibmc_dp_dev *dp_dev;
int ret;
dp_dev = devm_kzalloc(drm_dev->dev, sizeof(struct hibmc_dp_dev), GFP_KERNEL);
if (!dp_dev)
return -ENOMEM;
mutex_init(&dp_dev->lock);
dp->dp_dev = dp_dev;
dp_dev->dev = drm_dev;
dp_dev->base = dp->mmio + HIBMC_DP_OFFSET;
hibmc_dp_aux_init(dp);
ret = hibmc_dp_serdes_init(dp_dev);
if (ret)
return ret;
dp_dev->link.cap.lanes = 0x2;
dp_dev->link.cap.link_rate = DP_LINK_BW_8_1;
/* int init */
writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE);
writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS);
/* clr colorbar */
writel(0, dp_dev->base + HIBMC_DP_COLOR_BAR_CTRL);
/* rst */
writel(0, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL);
usleep_range(30, 50);
/* de-rst */
writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL);
/* hdcp data */
writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG);
/* clock enable */
writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL);
return 0;
}
void hibmc_dp_enable_int(struct hibmc_dp *dp)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
writel(HIBMC_DP_INT_ENABLE, dp_dev->base + HIBMC_DP_INTR_ENABLE);
}
void hibmc_dp_disable_int(struct hibmc_dp *dp)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE);
writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS);
}
void hibmc_dp_hpd_cfg(struct hibmc_dp *dp)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_SYNC_LEN_SEL, 0x0);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_TIMER_TIMEOUT, 0x1);
hibmc_dp_reg_write_field(dp->dp_dev, HIBMC_DP_AUX_REQ, HIBMC_DP_CFG_AUX_MIN_PULSE_NUM, 0x9);
writel(HIBMC_DP_HDCP, dp_dev->base + HIBMC_DP_HDCP_CFG);
writel(0, dp_dev->base + HIBMC_DP_INTR_ENABLE);
writel(HIBMC_DP_INT_RST, dp_dev->base + HIBMC_DP_INTR_ORIGINAL_STATUS);
writel(HIBMC_DP_INT_ENABLE, dp_dev->base + HIBMC_DP_INTR_ENABLE);
writel(HIBMC_DP_DPTX_RST, dp_dev->base + HIBMC_DP_DPTX_RST_CTRL);
writel(HIBMC_DP_CLK_EN, dp_dev->base + HIBMC_DP_DPTX_CLK_CTRL);
}
void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
if (enable) {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0x1);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0x1);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
} else {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_DPTX_GCTL0, BIT(10), 0);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_VIDEO_CTRL, BIT(0), 0);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
}
msleep(50);
}
int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
int ret;
if (!dp_dev->link.status.channel_equalized) {
ret = hibmc_dp_link_training(dp_dev);
if (ret) {
drm_err(dp->drm_dev, "dp link training failed, ret: %d\n", ret);
return ret;
}
}
hibmc_dp_display_en(dp, false);
hibmc_dp_link_cfg(dp_dev, mode);
return 0;
}
void hibmc_dp_reset_link(struct hibmc_dp *dp)
{
dp->dp_dev->link.status.clock_recovered = false;
dp->dp_dev->link.status.channel_equalized = false;
}
u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp)
{
return dp->dp_dev->link.cap.link_rate;
}
u8 hibmc_dp_get_lanes(struct hibmc_dp *dp)
{
return dp->dp_dev->link.cap.lanes;
}
static const struct hibmc_dp_color_raw g_rgb_raw[] = {
{CBAR_COLOR_BAR, 0x000, 0x000, 0x000},
{CBAR_WHITE, 0xfff, 0xfff, 0xfff},
{CBAR_RED, 0xfff, 0x000, 0x000},
{CBAR_ORANGE, 0xfff, 0x800, 0x000},
{CBAR_YELLOW, 0xfff, 0xfff, 0x000},
{CBAR_GREEN, 0x000, 0xfff, 0x000},
{CBAR_CYAN, 0x000, 0x800, 0x800},
{CBAR_BLUE, 0x000, 0x000, 0xfff},
{CBAR_PURPLE, 0x800, 0x000, 0x800},
{CBAR_BLACK, 0x000, 0x000, 0x000},
};
void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg)
{
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
struct hibmc_dp_color_raw raw_data;
if (cfg->enable) {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(9),
cfg->self_timing);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(8, 1),
cfg->dynamic_rate);
if (cfg->pattern == CBAR_COLOR_BAR) {
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 0);
} else {
raw_data = g_rgb_raw[cfg->pattern];
drm_dbg_dp(dp->drm_dev, "r:%x g:%x b:%x\n", raw_data.r_value,
raw_data.g_value, raw_data.b_value);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(10), 1);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, GENMASK(23, 12),
raw_data.r_value);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(23, 12),
raw_data.g_value);
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL1, GENMASK(11, 0),
raw_data.b_value);
}
}
hibmc_dp_reg_write_field(dp_dev, HIBMC_DP_COLOR_BAR_CTRL, BIT(0), cfg->enable);
writel(HIBMC_DP_SYNC_EN_MASK, dp_dev->base + HIBMC_DP_TIMING_SYNC_CTRL);
}
bool hibmc_dp_check_hpd_status(struct hibmc_dp *dp, int exp_status)
{
u32 status;
int ret;
ret = readl_poll_timeout(dp->dp_dev->base + HIBMC_DP_HPD_STATUS, status,
FIELD_GET(HIBMC_DP_HPD_CUR_STATE, status) == exp_status,
1000, 100000); /* DP spec says 100ms */
if (ret) {
drm_dbg_dp(dp->drm_dev, "wait hpd status timeout");
return false;
}
dp->dp_dev->hpd_status = exp_status;
return true;
}
@@ -0,0 +1,73 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_KAPI_H
#define DP_KAPI_H
#include <linux/types.h>
#include <linux/delay.h>
#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
#include <drm/drm_connector.h>
#include <drm/drm_print.h>
#include <drm/display/drm_dp_helper.h>
struct hibmc_dp_dev;
enum hibmc_hpd_status {
HIBMC_HPD_OUT,
HIBMC_HPD_IN,
};
enum hibmc_dp_cbar_pattern {
CBAR_COLOR_BAR,
CBAR_WHITE,
CBAR_RED,
CBAR_ORANGE,
CBAR_YELLOW,
CBAR_GREEN,
CBAR_CYAN,
CBAR_BLUE,
CBAR_PURPLE,
CBAR_BLACK,
};
struct hibmc_dp_color_raw {
enum hibmc_dp_cbar_pattern pattern;
u32 r_value;
u32 g_value;
u32 b_value;
};
struct hibmc_dp_cbar_cfg {
u8 enable;
u8 self_timing;
u8 dynamic_rate; /* 0:static, 1-255(frame):dynamic */
enum hibmc_dp_cbar_pattern pattern;
};
struct hibmc_dp {
struct hibmc_dp_dev *dp_dev;
struct drm_device *drm_dev;
struct drm_encoder encoder;
struct drm_connector connector;
void __iomem *mmio;
struct drm_dp_aux aux;
struct hibmc_dp_cbar_cfg cfg;
u32 irq_status;
int phys_status;
};
int hibmc_dp_hw_init(struct hibmc_dp *dp);
int hibmc_dp_mode_set(struct hibmc_dp *dp, struct drm_display_mode *mode);
void hibmc_dp_display_en(struct hibmc_dp *dp, bool enable);
void hibmc_dp_set_cbar(struct hibmc_dp *dp, const struct hibmc_dp_cbar_cfg *cfg);
void hibmc_dp_reset_link(struct hibmc_dp *dp);
void hibmc_dp_hpd_cfg(struct hibmc_dp *dp);
void hibmc_dp_enable_int(struct hibmc_dp *dp);
void hibmc_dp_disable_int(struct hibmc_dp *dp);
bool hibmc_dp_check_hpd_status(struct hibmc_dp *dp, int exp_status);
u8 hibmc_dp_get_link_rate(struct hibmc_dp *dp);
u8 hibmc_dp_get_lanes(struct hibmc_dp *dp);
#endif
@@ -0,0 +1,392 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/delay.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "dp_comm.h"
#include "dp_reg.h"
#define HIBMC_EQ_MAX_RETRY 5
static inline int hibmc_dp_get_serdes_rate_cfg(struct hibmc_dp_dev *dp)
{
switch (dp->link.cap.link_rate) {
case DP_LINK_BW_1_62:
return DP_SERDES_BW_1_62;
case DP_LINK_BW_2_7:
return DP_SERDES_BW_2_7;
case DP_LINK_BW_5_4:
return DP_SERDES_BW_5_4;
case DP_LINK_BW_8_1:
return DP_SERDES_BW_8_1;
default:
return -EINVAL;
}
}
static int hibmc_dp_link_training_configure(struct hibmc_dp_dev *dp)
{
u8 buf[2];
int ret;
/* DP 2 lane */
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_LANE_DATA_EN,
dp->link.cap.lanes == 0x2 ? 0x3 : 0x1);
hibmc_dp_reg_write_field(dp, HIBMC_DP_DPTX_GCTL0, HIBMC_DP_CFG_PHY_LANE_NUM,
dp->link.cap.lanes == 0x2 ? 0x1 : 0);
/* enhanced frame */
hibmc_dp_reg_write_field(dp, HIBMC_DP_VIDEO_CTRL, HIBMC_DP_CFG_STREAM_FRAME_MODE, 0x1);
/* set rate and lane count */
buf[0] = dp->link.cap.link_rate;
buf[1] = DP_LANE_COUNT_ENHANCED_FRAME_EN | dp->link.cap.lanes;
ret = drm_dp_dpcd_write(dp->aux, DP_LINK_BW_SET, buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write link rate and lanes failed, ret: %d\n", ret);
return ret >= 0 ? -EIO : ret;
}
/* set 8b/10b and downspread */
buf[0] = DP_SPREAD_AMP_0_5;
buf[1] = DP_SET_ANSI_8B10B;
ret = drm_dp_dpcd_write(dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write 8b/10b and downspread failed, ret: %d\n", ret);
return ret >= 0 ? -EIO : ret;
}
return 0;
}
static int hibmc_dp_link_set_pattern(struct hibmc_dp_dev *dp, int pattern)
{
int ret;
u8 val;
u8 buf;
buf = (u8)pattern;
if (pattern != DP_TRAINING_PATTERN_DISABLE && pattern != DP_TRAINING_PATTERN_4) {
buf |= DP_LINK_SCRAMBLING_DISABLE;
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0x1);
} else {
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_SCRAMBLE_EN, 0);
}
switch (pattern) {
case DP_TRAINING_PATTERN_DISABLE:
val = 0;
break;
case DP_TRAINING_PATTERN_1:
val = 1;
break;
case DP_TRAINING_PATTERN_2:
val = 2;
break;
case DP_TRAINING_PATTERN_3:
val = 3;
break;
case DP_TRAINING_PATTERN_4:
val = 4;
break;
default:
return -EINVAL;
}
hibmc_dp_reg_write_field(dp, HIBMC_DP_PHYIF_CTRL0, HIBMC_DP_CFG_PAT_SEL, val);
ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_PATTERN_SET, &buf, sizeof(buf));
if (ret != sizeof(buf)) {
drm_dbg_dp(dp->dev, "dp aux write training pattern set failed\n");
return ret >= 0 ? -EIO : ret;
}
return 0;
}
static int hibmc_dp_link_training_cr_pre(struct hibmc_dp_dev *dp)
{
u8 *train_set = dp->link.train_set;
int ret;
u8 i;
ret = hibmc_dp_link_training_configure(dp);
if (ret)
return ret;
ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_1);
if (ret)
return ret;
for (i = 0; i < dp->link.cap.lanes; i++)
train_set[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0;
ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set);
if (ret)
return ret;
ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, train_set, dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "dp aux write training lane set failed\n");
return ret >= 0 ? -EIO : ret;
}
return 0;
}
static bool hibmc_dp_link_get_adjust_train(struct hibmc_dp_dev *dp,
u8 lane_status[DP_LINK_STATUS_SIZE])
{
u8 train_set[HIBMC_DP_LANE_NUM_MAX] = {0};
u8 lane;
for (lane = 0; lane < dp->link.cap.lanes; lane++)
train_set[lane] = drm_dp_get_adjust_request_voltage(lane_status, lane) |
drm_dp_get_adjust_request_pre_emphasis(lane_status, lane);
if (memcmp(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX)) {
memcpy(dp->link.train_set, train_set, HIBMC_DP_LANE_NUM_MAX);
return true;
}
return false;
}
static int hibmc_dp_link_reduce_rate(struct hibmc_dp_dev *dp)
{
int ret;
switch (dp->link.cap.link_rate) {
case DP_LINK_BW_2_7:
dp->link.cap.link_rate = DP_LINK_BW_1_62;
break;
case DP_LINK_BW_5_4:
dp->link.cap.link_rate = DP_LINK_BW_2_7;
break;
case DP_LINK_BW_8_1:
dp->link.cap.link_rate = DP_LINK_BW_5_4;
break;
default:
return -EINVAL;
}
ret = hibmc_dp_get_serdes_rate_cfg(dp);
if (ret < 0)
return ret;
return hibmc_dp_serdes_rate_switch(ret, dp);
}
static inline int hibmc_dp_link_reduce_lane(struct hibmc_dp_dev *dp)
{
switch (dp->link.cap.lanes) {
case 0x2:
dp->link.cap.lanes--;
drm_dbg_dp(dp->dev, "dp link training reduce to 1 lane\n");
break;
case 0x1:
drm_err(dp->dev, "dp link training reduce lane failed, already reach minimum\n");
return -EIO;
default:
return -EINVAL;
}
return 0;
}
static int hibmc_dp_link_training_cr(struct hibmc_dp_dev *dp)
{
u8 lane_status[DP_LINK_STATUS_SIZE] = {0};
bool level_changed;
u32 voltage_tries;
u32 cr_tries;
int ret;
/*
* DP 1.4 spec define 10 for maxtries value, for pre DP 1.4 version set a limit of 80
* (4 voltage levels x 4 preemphasis levels x 5 identical voltage retries)
*/
voltage_tries = 1;
for (cr_tries = 0; cr_tries < 80; cr_tries++) {
drm_dp_link_train_clock_recovery_delay(dp->aux, dp->dpcd);
ret = drm_dp_dpcd_read_link_status(dp->aux, lane_status);
if (ret) {
drm_err(dp->dev, "Get lane status failed\n");
return ret;
}
if (drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) {
drm_dbg_dp(dp->dev, "dp link training cr done\n");
dp->link.status.clock_recovered = true;
return 0;
}
if (voltage_tries == 5) {
drm_dbg_dp(dp->dev, "same voltage tries 5 times\n");
dp->link.status.clock_recovered = false;
return 0;
}
level_changed = hibmc_dp_link_get_adjust_train(dp, lane_status);
ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set);
if (ret)
return ret;
ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET, dp->link.train_set,
dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "Update link training failed\n");
return ret >= 0 ? -EIO : ret;
}
voltage_tries = level_changed ? 1 : voltage_tries + 1;
}
drm_err(dp->dev, "dp link training clock recovery 80 times failed\n");
dp->link.status.clock_recovered = false;
return 0;
}
static int hibmc_dp_link_training_channel_eq(struct hibmc_dp_dev *dp)
{
u8 lane_status[DP_LINK_STATUS_SIZE] = {0};
u8 eq_tries;
int ret;
ret = hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_2);
if (ret)
return ret;
for (eq_tries = 0; eq_tries < HIBMC_EQ_MAX_RETRY; eq_tries++) {
drm_dp_link_train_channel_eq_delay(dp->aux, dp->dpcd);
ret = drm_dp_dpcd_read_link_status(dp->aux, lane_status);
if (ret) {
drm_err(dp->dev, "get lane status failed\n");
break;
}
if (!drm_dp_clock_recovery_ok(lane_status, dp->link.cap.lanes)) {
drm_dbg_dp(dp->dev, "clock recovery check failed\n");
drm_dbg_dp(dp->dev, "cannot continue channel equalization\n");
dp->link.status.clock_recovered = false;
break;
}
if (drm_dp_channel_eq_ok(lane_status, dp->link.cap.lanes)) {
dp->link.status.channel_equalized = true;
drm_dbg_dp(dp->dev, "dp link training eq done\n");
break;
}
hibmc_dp_link_get_adjust_train(dp, lane_status);
ret = hibmc_dp_serdes_set_tx_cfg(dp, dp->link.train_set);
if (ret)
return ret;
ret = drm_dp_dpcd_write(dp->aux, DP_TRAINING_LANE0_SET,
dp->link.train_set, dp->link.cap.lanes);
if (ret != dp->link.cap.lanes) {
drm_dbg_dp(dp->dev, "Update link training failed\n");
ret = (ret >= 0) ? -EIO : ret;
break;
}
}
if (eq_tries == HIBMC_EQ_MAX_RETRY)
drm_err(dp->dev, "channel equalization failed %u times\n", eq_tries);
hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
return ret < 0 ? ret : 0;
}
static int hibmc_dp_link_downgrade_training_cr(struct hibmc_dp_dev *dp)
{
if (hibmc_dp_link_reduce_rate(dp))
return hibmc_dp_link_reduce_lane(dp);
return 0;
}
static int hibmc_dp_link_downgrade_training_eq(struct hibmc_dp_dev *dp)
{
if ((dp->link.status.clock_recovered && !dp->link.status.channel_equalized)) {
if (!hibmc_dp_link_reduce_lane(dp))
return 0;
}
return hibmc_dp_link_reduce_rate(dp);
}
void hibmc_dp_update_caps(struct hibmc_dp_dev *dp)
{
dp->link.cap.link_rate = dp->dpcd[DP_MAX_LINK_RATE];
if (dp->link.cap.link_rate > DP_LINK_BW_8_1 || !dp->link.cap.link_rate)
dp->link.cap.link_rate = DP_LINK_BW_8_1;
dp->link.cap.lanes = dp->dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
if (dp->link.cap.lanes > HIBMC_DP_LANE_NUM_MAX)
dp->link.cap.lanes = HIBMC_DP_LANE_NUM_MAX;
}
int hibmc_dp_link_training(struct hibmc_dp_dev *dp)
{
struct hibmc_dp_link *link = &dp->link;
int ret;
ret = drm_dp_read_dpcd_caps(dp->aux, dp->dpcd);
if (ret)
drm_err(dp->dev, "dp aux read dpcd failed, ret: %d\n", ret);
hibmc_dp_update_caps(dp);
ret = hibmc_dp_get_serdes_rate_cfg(dp);
if (ret < 0)
return ret;
ret = hibmc_dp_serdes_rate_switch(ret, dp);
if (ret)
return ret;
while (true) {
ret = hibmc_dp_link_training_cr_pre(dp);
if (ret)
goto err;
ret = hibmc_dp_link_training_cr(dp);
if (ret)
goto err;
if (!link->status.clock_recovered) {
ret = hibmc_dp_link_downgrade_training_cr(dp);
if (ret)
goto err;
continue;
}
ret = hibmc_dp_link_training_channel_eq(dp);
if (ret)
goto err;
if (!link->status.channel_equalized) {
ret = hibmc_dp_link_downgrade_training_eq(dp);
if (ret)
goto err;
continue;
}
return 0;
}
err:
hibmc_dp_link_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE);
return ret;
}
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright (c) 2024 Hisilicon Limited. */
#ifndef DP_REG_H
#define DP_REG_H
#define HIBMC_DP_AUX_CMD_ADDR 0x50
#define HIBMC_DP_AUX_WR_DATA0 0x54
#define HIBMC_DP_AUX_WR_DATA1 0x58
#define HIBMC_DP_AUX_WR_DATA2 0x5c
#define HIBMC_DP_AUX_WR_DATA3 0x60
#define HIBMC_DP_AUX_RD_DATA0 0x64
#define HIBMC_DP_AUX_REQ 0x74
#define HIBMC_DP_CFG_AUX_REQ BIT(0)
#define HIBMC_DP_CFG_AUX_SYNC_LEN_SEL BIT(1)
#define HIBMC_DP_CFG_AUX_TIMER_TIMEOUT BIT(2)
#define HIBMC_DP_CFG_AUX_MIN_PULSE_NUM GENMASK(13, 9)
#define HIBMC_DP_AUX_STATUS 0x78
#define HIBMC_DP_CFG_AUX_TIMEOUT BIT(0)
#define HIBMC_DP_CFG_AUX_STATUS GENMASK(11, 4)
#define HIBMC_DP_CFG_AUX_READY_DATA_BYTE GENMASK(16, 12)
#define HIBMC_DP_CFG_AUX GENMASK(24, 17)
#define HIBMC_DP_HPD_STATUS 0x98
#define HIBMC_DP_HPD_CUR_STATE GENMASK(7, 4)
#define HIBMC_DP_PHYIF_CTRL0 0xa0
#define HIBMC_DP_CFG_SCRAMBLE_EN BIT(0)
#define HIBMC_DP_CFG_PAT_SEL GENMASK(7, 4)
#define HIBMC_DP_CFG_LANE_DATA_EN GENMASK(11, 8)
#define HIBMC_DP_VIDEO_CTRL 0x100
#define HIBMC_DP_CFG_STREAM_RGB_ENABLE BIT(1)
#define HIBMC_DP_CFG_STREAM_VIDEO_MAPPING GENMASK(5, 2)
#define HIBMC_DP_CFG_STREAM_FRAME_MODE BIT(6)
#define HIBMC_DP_CFG_STREAM_HSYNC_POLARITY BIT(7)
#define HIBMC_DP_CFG_STREAM_VSYNC_POLARITY BIT(8)
#define HIBMC_DP_VIDEO_CONFIG0 0x104
#define HIBMC_DP_CFG_STREAM_HACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HBLANK GENMASK(15, 0)
#define HIBMC_DP_VIDEO_CONFIG1 0x108
#define HIBMC_DP_CFG_STREAM_VACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_VBLANK GENMASK(15, 0)
#define HIBMC_DP_VIDEO_CONFIG2 0x10c
#define HIBMC_DP_CFG_STREAM_HSYNC_WIDTH GENMASK(15, 0)
#define HIBMC_DP_VIDEO_CONFIG3 0x110
#define HIBMC_DP_CFG_STREAM_VSYNC_WIDTH GENMASK(15, 0)
#define HIBMC_DP_CFG_STREAM_VFRONT_PORCH GENMASK(31, 16)
#define HIBMC_DP_VIDEO_PACKET 0x114
#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_SIZE GENMASK(5, 0)
#define HIBMC_DP_CFG_STREAM_TU_SYMBOL_FRAC_SIZE GENMASK(9, 6)
#define HIBMC_DP_CFG_STREAM_SYNC_CALIBRATION GENMASK(31, 20)
#define HIBMC_DP_VIDEO_MSA0 0x118
#define HIBMC_DP_CFG_STREAM_VSTART GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HSTART GENMASK(15, 0)
#define HIBMC_DP_VIDEO_MSA1 0x11c
#define HIBMC_DP_VIDEO_MSA2 0x120
#define HIBMC_DP_VIDEO_HORIZONTAL_SIZE 0X124
#define HIBMC_DP_CFG_STREAM_HTOTAL_SIZE GENMASK(31, 16)
#define HIBMC_DP_CFG_STREAM_HBLANK_SIZE GENMASK(15, 0)
#define HIBMC_DP_COLOR_BAR_CTRL 0x260
#define HIBMC_DP_COLOR_BAR_CTRL1 0x264
#define HIBMC_DP_TIMING_GEN_CONFIG0 0x26c
#define HIBMC_DP_CFG_TIMING_GEN0_HACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_TIMING_GEN0_HBLANK GENMASK(15, 0)
#define HIBMC_DP_TIMING_GEN_CONFIG2 0x274
#define HIBMC_DP_CFG_TIMING_GEN0_VACTIVE GENMASK(31, 16)
#define HIBMC_DP_CFG_TIMING_GEN0_VBLANK GENMASK(15, 0)
#define HIBMC_DP_TIMING_GEN_CONFIG3 0x278
#define HIBMC_DP_CFG_TIMING_GEN0_VFRONT_PORCH GENMASK(31, 16)
#define HIBMC_DP_HDCP_CFG 0x600
#define HIBMC_DP_DPTX_RST_CTRL 0x700
#define HIBMC_DP_CFG_AUX_RST_N BIT(4)
#define HIBMC_DP_DPTX_CLK_CTRL 0x704
#define HIBMC_DP_DPTX_GCTL0 0x708
#define HIBMC_DP_CFG_PHY_LANE_NUM GENMASK(2, 1)
#define HIBMC_DP_INTR_ENABLE 0x720
#define HIBMC_DP_INTR_ORIGINAL_STATUS 0x728
#define HIBMC_DP_TIMING_MODEL_CTRL 0x884
#define HIBMC_DP_CFG_PIXEL_NUM_TIMING_MODE_SEL1 GENMASK(31, 16)
#define HIBMC_DP_TIMING_SYNC_CTRL 0xFF0
#define HIBMC_DP_INTSTAT 0x1e0724
#define HIBMC_DP_INTCLR 0x1e0728
/* dp serdes reg */
#define HIBMC_DP_HOST_OFFSET 0x10000
#define HIBMC_DP_LANE0_RATE_OFFSET 0x4
#define HIBMC_DP_LANE1_RATE_OFFSET 0xc
#define HIBMC_DP_LANE_STATUS_OFFSET 0x10
#define HIBMC_DP_PMA_LANE0_OFFSET 0x18
#define HIBMC_DP_PMA_LANE1_OFFSET 0x1c
#define HIBMC_DP_HOST_SERDES_CTRL 0x1f001c
#define HIBMC_DP_PMA_TXDEEMPH GENMASK(18, 1)
#define DP_SERDES_DONE 0x3
/* dp serdes TX-Deempth Configuration */
#define DP_SERDES_VOL0_PRE0 0x280
#define DP_SERDES_VOL0_PRE1 0x2300
#define DP_SERDES_VOL0_PRE2 0x53c0
#define DP_SERDES_VOL0_PRE3 0x8400
#define DP_SERDES_VOL1_PRE0 0x380
#define DP_SERDES_VOL1_PRE1 0x3440
#define DP_SERDES_VOL1_PRE2 0x6480
#define DP_SERDES_VOL2_PRE0 0x4c1
#define DP_SERDES_VOL2_PRE1 0x4500
#define DP_SERDES_VOL3_PRE0 0x600
#define DP_SERDES_BW_8_1 0x3
#define DP_SERDES_BW_5_4 0x2
#define DP_SERDES_BW_2_7 0x1
#define DP_SERDES_BW_1_62 0x0
#endif
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2025 Hisilicon Limited.
#include <linux/delay.h>
#include <drm/drm_device.h>
#include <drm/drm_print.h>
#include "dp_comm.h"
#include "dp_config.h"
#include "dp_reg.h"
int hibmc_dp_serdes_set_tx_cfg(struct hibmc_dp_dev *dp, u8 train_set[HIBMC_DP_LANE_NUM_MAX])
{
static const u32 serdes_tx_cfg[4][4] = { {DP_SERDES_VOL0_PRE0, DP_SERDES_VOL0_PRE1,
DP_SERDES_VOL0_PRE2, DP_SERDES_VOL0_PRE3},
{DP_SERDES_VOL1_PRE0, DP_SERDES_VOL1_PRE1,
DP_SERDES_VOL1_PRE2}, {DP_SERDES_VOL2_PRE0,
DP_SERDES_VOL2_PRE1}, {DP_SERDES_VOL3_PRE0}};
int cfg[2];
int i;
for (i = 0; i < HIBMC_DP_LANE_NUM_MAX; i++) {
cfg[i] = serdes_tx_cfg[FIELD_GET(DP_TRAIN_VOLTAGE_SWING_MASK, train_set[i])]
[FIELD_GET(DP_TRAIN_PRE_EMPHASIS_MASK, train_set[i])];
if (!cfg[i])
return -EINVAL;
/* lane1 offset is 4 */
writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, cfg[i]),
dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET + i * 4);
}
usleep_range(300, 500);
if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) {
drm_dbg_dp(dp->dev, "dp serdes cfg failed\n");
return -EAGAIN;
}
return 0;
}
int hibmc_dp_serdes_rate_switch(u8 rate, struct hibmc_dp_dev *dp)
{
writel(rate, dp->serdes_base + HIBMC_DP_LANE0_RATE_OFFSET);
writel(rate, dp->serdes_base + HIBMC_DP_LANE1_RATE_OFFSET);
usleep_range(300, 500);
if (readl(dp->serdes_base + HIBMC_DP_LANE_STATUS_OFFSET) != DP_SERDES_DONE) {
drm_dbg_dp(dp->dev, "dp serdes rate switching failed\n");
return -EAGAIN;
}
if (rate < DP_SERDES_BW_8_1)
drm_dbg_dp(dp->dev, "reducing serdes rate to :%d\n",
rate ? rate * HIBMC_DP_LINK_RATE_CAL * 10 : 162);
return 0;
}
int hibmc_dp_serdes_init(struct hibmc_dp_dev *dp)
{
dp->serdes_base = dp->base + HIBMC_DP_HOST_OFFSET;
writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0),
dp->serdes_base + HIBMC_DP_PMA_LANE0_OFFSET);
writel(FIELD_PREP(HIBMC_DP_PMA_TXDEEMPH, DP_SERDES_VOL0_PRE0),
dp->serdes_base + HIBMC_DP_PMA_LANE1_OFFSET);
return hibmc_dp_serdes_rate_switch(DP_SERDES_BW_8_1, dp);
}