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,16 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_HISI_HIBMC
tristate "DRM Support for Hisilicon Hibmc"
depends on DRM && PCI
select DRM_CLIENT_SELECTION
select DRM_DISPLAY_HELPER
select DRM_DISPLAY_DP_HELPER
select DRM_KMS_HELPER
select DRM_VRAM_HELPER
select DRM_TTM
select DRM_TTM_HELPER
select I2C
select I2C_ALGOBIT
help
Choose this option if you have a Hisilicon Hibmc soc chipset.
If M is selected the module will be called hibmc-drm.
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
hibmc-drm-y := hibmc_drm_drv.o hibmc_drm_de.o hibmc_drm_vdac.o hibmc_drm_i2c.o \
dp/dp_aux.o dp/dp_link.o dp/dp_hw.o dp/dp_serdes.o hibmc_drm_dp.o \
hibmc_drm_debugfs.o
obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc-drm.o
@@ -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);
}
@@ -0,0 +1,548 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Rongrong Zou <zourongrong@huawei.com>
* Rongrong Zou <zourongrong@gmail.com>
* Jianhua Li <lijianhua@huawei.com>
*/
#include <linux/delay.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_vblank.h>
#include "hibmc_drm_drv.h"
#include "hibmc_drm_regs.h"
struct hibmc_display_panel_pll {
u64 M;
u64 N;
u64 OD;
u64 POD;
};
struct hibmc_dislay_pll_config {
u64 hdisplay;
u64 vdisplay;
int clock;
u32 pll1_config_value;
u32 pll2_config_value;
};
static const struct hibmc_dislay_pll_config hibmc_pll_table[] = {
{640, 480, 25000, CRT_PLL1_HS_25MHZ, CRT_PLL2_HS_25MHZ},
{800, 600, 40000, CRT_PLL1_HS_40MHZ, CRT_PLL2_HS_40MHZ},
{1024, 768, 65000, CRT_PLL1_HS_65MHZ, CRT_PLL2_HS_65MHZ},
{1152, 864, 78750, CRT_PLL1_HS_80MHZ_1152, CRT_PLL2_HS_80MHZ},
{1280, 768, 80000, CRT_PLL1_HS_80MHZ, CRT_PLL2_HS_80MHZ},
{1280, 720, 74375, CRT_PLL1_HS_74MHZ, CRT_PLL2_HS_74MHZ},
{1280, 960, 108000, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1280, 1024, 108000, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1440, 900, 105952, CRT_PLL1_HS_106MHZ, CRT_PLL2_HS_106MHZ},
{1600, 900, 108000, CRT_PLL1_HS_108MHZ, CRT_PLL2_HS_108MHZ},
{1600, 1200, 162500, CRT_PLL1_HS_162MHZ, CRT_PLL2_HS_162MHZ},
{1920, 1080, 148750, CRT_PLL1_HS_148MHZ, CRT_PLL2_HS_148MHZ},
{1920, 1200, 193750, CRT_PLL1_HS_193MHZ, CRT_PLL2_HS_193MHZ},
};
static int hibmc_get_best_clock_idx(const struct drm_display_mode *mode)
{
int i, diff;
for (i = 0; i < ARRAY_SIZE(hibmc_pll_table); i++) {
if (hibmc_pll_table[i].hdisplay == mode->hdisplay &&
hibmc_pll_table[i].vdisplay == mode->vdisplay) {
diff = abs(mode->clock - hibmc_pll_table[i].clock);
if (diff < mode->clock / 100) /* tolerance 1/100 */
return i;
}
}
return -MODE_CLOCK_RANGE;
}
static int hibmc_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct drm_framebuffer *fb = new_plane_state->fb;
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_crtc_state *crtc_state;
u32 src_w = new_plane_state->src_w >> 16;
u32 src_h = new_plane_state->src_h >> 16;
if (!crtc || !fb)
return 0;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
if (src_w != new_plane_state->crtc_w || src_h != new_plane_state->crtc_h) {
drm_dbg_atomic(plane->dev, "scale not support\n");
return -EINVAL;
}
if (new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0) {
drm_dbg_atomic(plane->dev, "crtc_x/y of drm_plane state is invalid\n");
return -EINVAL;
}
if (!crtc_state->enable)
return 0;
if (new_plane_state->crtc_x + new_plane_state->crtc_w >
crtc_state->adjusted_mode.hdisplay ||
new_plane_state->crtc_y + new_plane_state->crtc_h >
crtc_state->adjusted_mode.vdisplay) {
drm_dbg_atomic(plane->dev, "visible portion of plane is invalid\n");
return -EINVAL;
}
if (new_plane_state->fb->pitches[0] % 128 != 0) {
drm_dbg_atomic(plane->dev, "wrong stride with 128-byte aligned\n");
return -EINVAL;
}
return 0;
}
static void hibmc_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
u32 reg;
s64 gpu_addr = 0;
u32 line_l;
struct hibmc_drm_private *priv = to_hibmc_drm_private(plane->dev);
struct drm_gem_vram_object *gbo;
if (!new_state->fb)
return;
gbo = drm_gem_vram_of_gem(new_state->fb->obj[0]);
gpu_addr = drm_gem_vram_offset(gbo);
if (WARN_ON_ONCE(gpu_addr < 0))
return; /* Bug: we didn't pin the BO to VRAM in prepare_fb. */
writel(gpu_addr, priv->mmio + HIBMC_CRT_FB_ADDRESS);
reg = new_state->fb->width * (new_state->fb->format->cpp[0]);
line_l = new_state->fb->pitches[0];
writel(HIBMC_FIELD(HIBMC_CRT_FB_WIDTH_WIDTH, reg) |
HIBMC_FIELD(HIBMC_CRT_FB_WIDTH_OFFS, line_l),
priv->mmio + HIBMC_CRT_FB_WIDTH);
/* SET PIXEL FORMAT */
reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL);
reg &= ~HIBMC_CRT_DISP_CTL_FORMAT_MASK;
reg |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_FORMAT,
new_state->fb->format->cpp[0] * 8 / 16);
writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL);
}
static const u32 channel_formats1[] = {
DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_RGB888,
DRM_FORMAT_BGR888, DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888, DRM_FORMAT_ARGB8888,
DRM_FORMAT_ABGR8888
};
static const struct drm_plane_funcs hibmc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static const struct drm_plane_helper_funcs hibmc_plane_helper_funcs = {
DRM_GEM_VRAM_PLANE_HELPER_FUNCS,
.atomic_check = hibmc_plane_atomic_check,
.atomic_update = hibmc_plane_atomic_update,
};
static void hibmc_crtc_dpms(struct drm_crtc *crtc, u32 dpms)
{
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
u32 reg;
reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL);
reg &= ~HIBMC_CRT_DISP_CTL_DPMS_MASK;
reg |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_DPMS, dpms);
reg &= ~HIBMC_CRT_DISP_CTL_TIMING_MASK;
if (dpms == HIBMC_CRT_DPMS_ON)
reg |= HIBMC_CRT_DISP_CTL_TIMING(1);
writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL);
}
static void hibmc_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
u32 reg;
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0);
/* Enable display power gate & LOCALMEM power gate*/
reg = readl(priv->mmio + HIBMC_CURRENT_GATE);
reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK;
reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK;
reg |= HIBMC_CURR_GATE_LOCALMEM(1);
reg |= HIBMC_CURR_GATE_DISPLAY(1);
hibmc_set_current_gate(priv, reg);
drm_crtc_vblank_on(crtc);
hibmc_crtc_dpms(crtc, HIBMC_CRT_DPMS_ON);
}
static void hibmc_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
u32 reg;
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
hibmc_crtc_dpms(crtc, HIBMC_CRT_DPMS_OFF);
drm_crtc_vblank_off(crtc);
hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_SLEEP);
/* Enable display power gate & LOCALMEM power gate*/
reg = readl(priv->mmio + HIBMC_CURRENT_GATE);
reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK;
reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK;
reg |= HIBMC_CURR_GATE_LOCALMEM(0);
reg |= HIBMC_CURR_GATE_DISPLAY(0);
hibmc_set_current_gate(priv, reg);
}
static enum drm_mode_status
hibmc_crtc_mode_valid(struct drm_crtc *crtc,
const struct drm_display_mode *mode)
{
int vrefresh = drm_mode_vrefresh(mode);
if (vrefresh < 59 || vrefresh > 61)
return MODE_NOCLOCK;
if (hibmc_get_best_clock_idx(mode) >= 0)
return MODE_OK;
return MODE_CLOCK_RANGE;
}
static u32 format_pll_reg(void)
{
u32 pllreg = 0;
struct hibmc_display_panel_pll pll = {0};
/*
* Note that all PLL's have the same format. Here,
* we just use Panel PLL parameter to work out the bit
* fields in the register.On returning a 32 bit number, the value can
* be applied to any PLL in the calling function.
*/
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_BYPASS, 0);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_POWER, 1);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_INPUT, 0);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_POD, pll.POD);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_OD, pll.OD);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_N, pll.N);
pllreg |= HIBMC_FIELD(HIBMC_PLL_CTRL_M, pll.M);
return pllreg;
}
static void set_vclock_hisilicon(struct drm_device *dev, u64 pll)
{
u32 val;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
val = readl(priv->mmio + CRT_PLL1_HS);
val &= ~(CRT_PLL1_HS_OUTER_BYPASS(1));
writel(val, priv->mmio + CRT_PLL1_HS);
val = CRT_PLL1_HS_INTER_BYPASS(1) | CRT_PLL1_HS_POWERON(1);
writel(val, priv->mmio + CRT_PLL1_HS);
writel(pll, priv->mmio + CRT_PLL1_HS);
usleep_range(1000, 2000);
val = pll & ~(CRT_PLL1_HS_POWERON(1));
writel(val, priv->mmio + CRT_PLL1_HS);
usleep_range(1000, 2000);
val &= ~(CRT_PLL1_HS_INTER_BYPASS(1));
writel(val, priv->mmio + CRT_PLL1_HS);
usleep_range(1000, 2000);
val |= CRT_PLL1_HS_OUTER_BYPASS(1);
writel(val, priv->mmio + CRT_PLL1_HS);
}
static void get_pll_config(struct drm_display_mode *mode, u32 *pll1, u32 *pll2)
{
int idx;
idx = hibmc_get_best_clock_idx(mode);
if (idx < 0) {
/* if found none, we use default value */
*pll1 = CRT_PLL1_HS_25MHZ;
*pll2 = CRT_PLL2_HS_25MHZ;
return;
}
*pll1 = hibmc_pll_table[idx].pll1_config_value;
*pll2 = hibmc_pll_table[idx].pll2_config_value;
}
/*
* This function takes care the extra registers and bit fields required to
* setup a mode in board.
* Explanation about Display Control register:
* FPGA only supports 7 predefined pixel clocks, and clock select is
* in bit 4:0 of new register 0x802a8.
*/
static u32 display_ctrl_adjust(struct drm_device *dev,
struct drm_display_mode *mode,
u32 ctrl)
{
u64 x, y;
u32 pll1; /* bit[31:0] of PLL */
u32 pll2; /* bit[63:32] of PLL */
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
x = mode->hdisplay;
y = mode->vdisplay;
get_pll_config(mode, &pll1, &pll2);
writel(pll2, priv->mmio + CRT_PLL2_HS);
set_vclock_hisilicon(dev, pll1);
/*
* Hisilicon has to set up the top-left and bottom-right
* registers as well.
* Note that normal chip only use those two register for
* auto-centering mode.
*/
writel(HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_TL_TOP, 0) |
HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_TL_LEFT, 0),
priv->mmio + HIBMC_CRT_AUTO_CENTERING_TL);
writel(HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM, y - 1) |
HIBMC_FIELD(HIBMC_CRT_AUTO_CENTERING_BR_RIGHT, x - 1),
priv->mmio + HIBMC_CRT_AUTO_CENTERING_BR);
/*
* Assume common fields in ctrl have been properly set before
* calling this function.
* This function only sets the extra fields in ctrl.
*/
/* Set bit 25 of display controller: Select CRT or VGA clock */
ctrl &= ~HIBMC_CRT_DISP_CTL_CRTSELECT_MASK;
ctrl &= ~HIBMC_CRT_DISP_CTL_CLOCK_PHASE_MASK;
ctrl |= HIBMC_CRT_DISP_CTL_CRTSELECT(HIBMC_CRTSELECT_CRT);
/* clock_phase_polarity is 0 */
ctrl |= HIBMC_CRT_DISP_CTL_CLOCK_PHASE(0);
writel(ctrl, priv->mmio + HIBMC_CRT_DISP_CTL);
return ctrl;
}
static void hibmc_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
u32 val;
struct drm_display_mode *mode = &crtc->state->mode;
struct drm_device *dev = crtc->dev;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
u32 width = mode->hsync_end - mode->hsync_start;
u32 height = mode->vsync_end - mode->vsync_start;
writel(format_pll_reg(), priv->mmio + HIBMC_CRT_PLL_CTRL);
writel(HIBMC_FIELD(HIBMC_CRT_HORZ_TOTAL_TOTAL, mode->htotal - 1) |
HIBMC_FIELD(HIBMC_CRT_HORZ_TOTAL_DISP_END, mode->hdisplay - 1),
priv->mmio + HIBMC_CRT_HORZ_TOTAL);
writel(HIBMC_FIELD(HIBMC_CRT_HORZ_SYNC_WIDTH, width) |
HIBMC_FIELD(HIBMC_CRT_HORZ_SYNC_START, mode->hsync_start - 1),
priv->mmio + HIBMC_CRT_HORZ_SYNC);
writel(HIBMC_FIELD(HIBMC_CRT_VERT_TOTAL_TOTAL, mode->vtotal - 1) |
HIBMC_FIELD(HIBMC_CRT_VERT_TOTAL_DISP_END, mode->vdisplay - 1),
priv->mmio + HIBMC_CRT_VERT_TOTAL);
writel(HIBMC_FIELD(HIBMC_CRT_VERT_SYNC_HEIGHT, height) |
HIBMC_FIELD(HIBMC_CRT_VERT_SYNC_START, mode->vsync_start - 1),
priv->mmio + HIBMC_CRT_VERT_SYNC);
val = HIBMC_FIELD(HIBMC_CRT_DISP_CTL_VSYNC_PHASE, 0);
val |= HIBMC_FIELD(HIBMC_CRT_DISP_CTL_HSYNC_PHASE, 0);
val |= HIBMC_CRT_DISP_CTL_TIMING(1);
val |= HIBMC_CRT_DISP_CTL_PLANE(1);
display_ctrl_adjust(dev, mode, val);
}
static void hibmc_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
u32 reg;
struct drm_device *dev = crtc->dev;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0);
/* Enable display power gate & LOCALMEM power gate*/
reg = readl(priv->mmio + HIBMC_CURRENT_GATE);
reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK;
reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK;
reg |= HIBMC_CURR_GATE_DISPLAY(1);
reg |= HIBMC_CURR_GATE_LOCALMEM(1);
hibmc_set_current_gate(priv, reg);
/* We can add more initialization as needed. */
}
static void hibmc_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
unsigned long flags;
spin_lock_irqsave(&crtc->dev->event_lock, flags);
if (crtc->state->event)
drm_crtc_send_vblank_event(crtc, crtc->state->event);
crtc->state->event = NULL;
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
static int hibmc_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
writel(HIBMC_RAW_INTERRUPT_EN_VBLANK(1),
priv->mmio + HIBMC_RAW_INTERRUPT_EN);
return 0;
}
static void hibmc_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
writel(HIBMC_RAW_INTERRUPT_EN_VBLANK(0),
priv->mmio + HIBMC_RAW_INTERRUPT_EN);
}
static void hibmc_crtc_load_lut(struct drm_crtc *crtc)
{
struct hibmc_drm_private *priv = to_hibmc_drm_private(crtc->dev);
void __iomem *mmio = priv->mmio;
u16 *r, *g, *b;
u32 reg;
u32 i;
r = crtc->gamma_store;
g = r + crtc->gamma_size;
b = g + crtc->gamma_size;
for (i = 0; i < crtc->gamma_size; i++) {
u32 offset = i << 2;
u8 red = *r++ >> 8;
u8 green = *g++ >> 8;
u8 blue = *b++ >> 8;
u32 rgb = (red << 16) | (green << 8) | blue;
writel(rgb, mmio + HIBMC_CRT_PALETTE + offset);
}
reg = readl(priv->mmio + HIBMC_CRT_DISP_CTL);
reg |= HIBMC_FIELD(HIBMC_CTL_DISP_CTL_GAMMA, 1);
writel(reg, priv->mmio + HIBMC_CRT_DISP_CTL);
}
static int hibmc_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green,
u16 *blue, uint32_t size,
struct drm_modeset_acquire_ctx *ctx)
{
hibmc_crtc_load_lut(crtc);
return 0;
}
static const struct drm_crtc_funcs hibmc_crtc_funcs = {
.page_flip = drm_atomic_helper_page_flip,
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = hibmc_crtc_enable_vblank,
.disable_vblank = hibmc_crtc_disable_vblank,
.gamma_set = hibmc_crtc_gamma_set,
};
static const struct drm_crtc_helper_funcs hibmc_crtc_helper_funcs = {
.mode_set_nofb = hibmc_crtc_mode_set_nofb,
.atomic_begin = hibmc_crtc_atomic_begin,
.atomic_flush = hibmc_crtc_atomic_flush,
.atomic_enable = hibmc_crtc_atomic_enable,
.atomic_disable = hibmc_crtc_atomic_disable,
.mode_valid = hibmc_crtc_mode_valid,
};
int hibmc_de_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct drm_crtc *crtc = &priv->crtc;
struct drm_plane *plane = &priv->primary_plane;
int ret;
ret = drm_universal_plane_init(dev, plane, 1, &hibmc_plane_funcs,
channel_formats1,
ARRAY_SIZE(channel_formats1),
NULL,
DRM_PLANE_TYPE_PRIMARY,
NULL);
if (ret) {
drm_err(dev, "failed to init plane: %d\n", ret);
return ret;
}
drm_plane_helper_add(plane, &hibmc_plane_helper_funcs);
ret = drm_crtc_init_with_planes(dev, crtc, plane,
NULL, &hibmc_crtc_funcs, NULL);
if (ret) {
drm_err(dev, "failed to init crtc: %d\n", ret);
return ret;
}
ret = drm_mode_crtc_set_gamma_size(crtc, 256);
if (ret) {
drm_err(dev, "failed to set gamma size: %d\n", ret);
return ret;
}
drm_crtc_helper_add(crtc, &hibmc_crtc_helper_funcs);
return 0;
}
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/pci.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_edid.h>
#include "hibmc_drm_drv.h"
#define MAX_BUF_SIZE 12
static ssize_t hibmc_control_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct hibmc_drm_private *priv = file_inode(file)->i_private;
struct hibmc_dp_cbar_cfg *cfg = &priv->dp.cfg;
int ret, idx;
u8 buf[MAX_BUF_SIZE];
if (count >= MAX_BUF_SIZE)
return -EINVAL;
if (copy_from_user(buf, user_buf, count))
return -EFAULT;
buf[count] = '\0';
/* Only 4 parameters is allowed, the ranger are as follow:
* [0] enable/disable colorbar feature
0: enable colorbar, 1: disable colorbar
* [1] the timing source of colorbar displaying
0: timing follows XDP, 1: internal self timing
* [2] the movment of colorbar displaying
0: static colorbar image,
* 1~255: right shifting a type of color per (1~255)frames
* [3] the color type of colorbar displaying
0~9: color bar, white, red, orange,
* yellow, green, cyan, bule, pupper, black
*/
if (sscanf(buf, "%hhu %hhu %hhu %u", &cfg->enable, &cfg->self_timing,
&cfg->dynamic_rate, &cfg->pattern) != 4) {
return -EINVAL;
}
if (cfg->pattern > 9 || cfg->enable > 1 || cfg->self_timing > 1)
return -EINVAL;
ret = drm_dev_enter(&priv->dev, &idx);
if (!ret)
return -ENODEV;
hibmc_dp_set_cbar(&priv->dp, cfg);
drm_dev_exit(idx);
return count;
}
static int hibmc_dp_dbgfs_show(struct seq_file *m, void *arg)
{
struct hibmc_drm_private *priv = m->private;
struct hibmc_dp_cbar_cfg *cfg = &priv->dp.cfg;
int idx;
if (!drm_dev_enter(&priv->dev, &idx))
return -ENODEV;
seq_printf(m, "hibmc dp colorbar cfg: %u %u %u %u\n", cfg->enable, cfg->self_timing,
cfg->dynamic_rate, cfg->pattern);
drm_dev_exit(idx);
return 0;
}
static int hibmc_open(struct inode *inode, struct file *filp)
{
return single_open(filp, hibmc_dp_dbgfs_show, inode->i_private);
}
static const struct file_operations hibmc_dbg_fops = {
.owner = THIS_MODULE,
.write = hibmc_control_write,
.read = seq_read,
.open = hibmc_open,
.llseek = seq_lseek,
.release = single_release,
};
void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root)
{
struct drm_device *dev = connector->dev;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
/* create the file in drm directory, so we don't need to remove manually */
debugfs_create_file("colorbar-cfg", 0200,
root, priv, &hibmc_dbg_fops);
}
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Hisilicon Limited.
#include <linux/io.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_modes.h>
#include <drm/drm_drv.h>
#include <drm/drm_edid.h>
#include "hibmc_drm_drv.h"
#include "dp/dp_hw.h"
#include "dp/dp_comm.h"
#include "dp/dp_config.h"
#define DP_MASKED_SINK_HPD_PLUG_INT BIT(2)
static int hibmc_dp_connector_get_modes(struct drm_connector *connector)
{
const struct drm_edid *drm_edid;
int count;
drm_edid = drm_edid_read(connector);
drm_edid_connector_update(connector, drm_edid);
count = drm_edid_connector_add_modes(connector);
drm_edid_free(drm_edid);
return count;
}
static bool hibmc_dp_get_dpcd(struct hibmc_dp_dev *dp_dev)
{
int ret;
ret = drm_dp_read_dpcd_caps(dp_dev->aux, dp_dev->dpcd);
if (ret)
return false;
hibmc_dp_update_caps(dp_dev);
dp_dev->is_branch = drm_dp_is_branch(dp_dev->dpcd);
ret = drm_dp_read_desc(dp_dev->aux, &dp_dev->desc, dp_dev->is_branch);
if (ret)
return false;
ret = drm_dp_read_downstream_info(dp_dev->aux, dp_dev->dpcd, dp_dev->downstream_ports);
if (ret)
return false;
return true;
}
static int hibmc_dp_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx, bool force)
{
struct hibmc_dp *dp = to_hibmc_dp(connector);
struct hibmc_dp_dev *dp_dev = dp->dp_dev;
int ret = connector_status_disconnected;
if (dp->irq_status) {
if (dp_dev->hpd_status != HIBMC_HPD_IN) {
ret = connector_status_disconnected;
goto exit;
}
}
if (!hibmc_dp_get_dpcd(dp_dev)) {
ret = connector_status_disconnected;
goto exit;
}
if (!dp_dev->is_branch) {
ret = connector_status_connected;
goto exit;
}
if (drm_dp_read_sink_count_cap(connector, dp_dev->dpcd, &dp_dev->desc) &&
dp_dev->downstream_ports[0] & DP_DS_PORT_HPD) {
ret = drm_dp_read_sink_count(dp_dev->aux);
if (ret > 0) {
ret = connector_status_connected;
goto exit;
}
}
exit:
dp->phys_status = ret;
return ret;
}
static int hibmc_dp_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode,
struct drm_modeset_acquire_ctx *ctx,
enum drm_mode_status *status)
{
struct hibmc_dp *dp = to_hibmc_dp(connector);
u64 cur_val, max_val;
/* check DP link BW */
cur_val = (u64)mode->clock * HIBMC_DP_BPP;
max_val = (u64)hibmc_dp_get_link_rate(dp) * DP_MODE_VALI_CAL * hibmc_dp_get_lanes(dp);
*status = cur_val > max_val ? MODE_CLOCK_HIGH : MODE_OK;
return 0;
}
static const struct drm_connector_helper_funcs hibmc_dp_conn_helper_funcs = {
.get_modes = hibmc_dp_connector_get_modes,
.detect_ctx = hibmc_dp_detect,
.mode_valid_ctx = hibmc_dp_mode_valid,
};
static int hibmc_dp_late_register(struct drm_connector *connector)
{
struct hibmc_dp *dp = to_hibmc_dp(connector);
hibmc_dp_enable_int(dp);
return drm_dp_aux_register(&dp->aux);
}
static void hibmc_dp_early_unregister(struct drm_connector *connector)
{
struct hibmc_dp *dp = to_hibmc_dp(connector);
drm_dp_aux_unregister(&dp->aux);
hibmc_dp_disable_int(dp);
}
static const struct drm_connector_funcs hibmc_dp_conn_funcs = {
.reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
.late_register = hibmc_dp_late_register,
.early_unregister = hibmc_dp_early_unregister,
.debugfs_init = hibmc_debugfs_init,
};
static inline int hibmc_dp_prepare(struct hibmc_dp *dp, struct drm_display_mode *mode)
{
int ret;
hibmc_dp_display_en(dp, false);
ret = hibmc_dp_mode_set(dp, mode);
if (ret)
drm_err(dp->drm_dev, "hibmc dp mode set failed: %d\n", ret);
return ret;
}
static void hibmc_dp_encoder_enable(struct drm_encoder *drm_encoder,
struct drm_atomic_commit *state)
{
struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder);
struct drm_display_mode *mode = &drm_encoder->crtc->state->mode;
if (hibmc_dp_prepare(dp, mode))
return;
hibmc_dp_display_en(dp, true);
}
static void hibmc_dp_encoder_disable(struct drm_encoder *drm_encoder,
struct drm_atomic_commit *state)
{
struct hibmc_dp *dp = container_of(drm_encoder, struct hibmc_dp, encoder);
hibmc_dp_display_en(dp, false);
}
static const struct drm_encoder_helper_funcs hibmc_dp_encoder_helper_funcs = {
.atomic_enable = hibmc_dp_encoder_enable,
.atomic_disable = hibmc_dp_encoder_disable,
};
irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg)
{
struct drm_device *dev = (struct drm_device *)arg;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
int idx, exp_status;
if (!drm_dev_enter(dev, &idx))
return -ENODEV;
if (priv->dp.irq_status & DP_MASKED_SINK_HPD_PLUG_INT) {
drm_dbg_dp(&priv->dev, "HPD IN isr occur!\n");
hibmc_dp_hpd_cfg(&priv->dp);
exp_status = HIBMC_HPD_IN;
} else {
drm_dbg_dp(&priv->dev, "HPD OUT isr occur!\n");
hibmc_dp_reset_link(&priv->dp);
exp_status = HIBMC_HPD_OUT;
}
if (hibmc_dp_check_hpd_status(&priv->dp, exp_status))
drm_connector_helper_hpd_irq_event(&priv->dp.connector);
drm_dev_exit(idx);
return IRQ_HANDLED;
}
int hibmc_dp_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct drm_crtc *crtc = &priv->crtc;
struct hibmc_dp *dp = &priv->dp;
struct drm_connector *connector = &dp->connector;
struct drm_encoder *encoder = &dp->encoder;
int ret;
dp->mmio = priv->mmio;
dp->drm_dev = dev;
ret = hibmc_dp_hw_init(&priv->dp);
if (ret) {
drm_err(dev, "hibmc dp hw init failed: %d\n", ret);
return ret;
}
hibmc_dp_display_en(&priv->dp, false);
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
if (ret) {
drm_err(dev, "init dp encoder failed: %d\n", ret);
return ret;
}
drm_encoder_helper_add(encoder, &hibmc_dp_encoder_helper_funcs);
ret = drm_connector_init_with_ddc(dev, connector, &hibmc_dp_conn_funcs,
DRM_MODE_CONNECTOR_DisplayPort, &dp->aux.ddc);
if (ret) {
drm_err(dev, "init dp connector failed: %d\n", ret);
return ret;
}
drm_connector_helper_add(connector, &hibmc_dp_conn_helper_funcs);
drm_connector_attach_encoder(connector, encoder);
connector->polled = DRM_CONNECTOR_POLL_HPD;
dp->phys_status = connector_status_disconnected;
return 0;
}
@@ -0,0 +1,464 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Rongrong Zou <zourongrong@huawei.com>
* Rongrong Zou <zourongrong@gmail.com>
* Jianhua Li <lijianhua@huawei.com>
*/
#include <linux/aperture.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_ttm.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_managed.h>
#include <drm/drm_module.h>
#include <drm/drm_vblank.h>
#include <drm/drm_probe_helper.h>
#include "hibmc_drm_drv.h"
#include "hibmc_drm_regs.h"
#include "dp/dp_reg.h"
DEFINE_DRM_GEM_FOPS(hibmc_fops);
static const char *g_irqs_names_map[HIBMC_MAX_VECTORS] = { "hibmc-vblank", "hibmc-hpd" };
static irqreturn_t hibmc_interrupt(int irq, void *arg)
{
struct drm_device *dev = (struct drm_device *)arg;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
u32 status;
status = readl(priv->mmio + HIBMC_RAW_INTERRUPT);
if (status & HIBMC_RAW_INTERRUPT_VBLANK(1)) {
writel(HIBMC_RAW_INTERRUPT_VBLANK(1),
priv->mmio + HIBMC_RAW_INTERRUPT);
drm_handle_vblank(dev, 0);
}
return IRQ_HANDLED;
}
static irqreturn_t hibmc_dp_interrupt(int irq, void *arg)
{
struct drm_device *dev = (struct drm_device *)arg;
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
u32 status;
status = readl(priv->mmio + HIBMC_DP_INTSTAT);
if (status) {
priv->dp.irq_status = status;
writel(status, priv->mmio + HIBMC_DP_INTCLR);
return IRQ_WAKE_THREAD;
}
return IRQ_HANDLED;
}
static int hibmc_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
return drm_gem_vram_fill_create_dumb(file, dev, 0, 128, args);
}
static const struct drm_driver hibmc_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &hibmc_fops,
.name = "hibmc",
.desc = "hibmc drm driver",
.major = 1,
.minor = 0,
.debugfs_init = drm_vram_mm_debugfs_init,
.dumb_create = hibmc_dumb_create,
.dumb_map_offset = drm_gem_ttm_dumb_map_offset,
DRM_FBDEV_TTM_DRIVER_OPS,
};
static int __maybe_unused hibmc_pm_suspend(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(drm_dev);
}
static int __maybe_unused hibmc_pm_resume(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(drm_dev);
}
static const struct dev_pm_ops hibmc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(hibmc_pm_suspend,
hibmc_pm_resume)
};
static const struct drm_mode_config_funcs hibmc_mode_funcs = {
.mode_valid = drm_vram_helper_mode_valid,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
.fb_create = drm_gem_fb_create,
};
static int hibmc_kms_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct drm_encoder *encoder;
u32 clone_mask = 0;
int ret;
ret = drmm_mode_config_init(dev);
if (ret)
return ret;
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.max_width = 1920;
dev->mode_config.max_height = 1200;
dev->mode_config.preferred_depth = 24;
dev->mode_config.prefer_shadow = 1;
dev->mode_config.funcs = (void *)&hibmc_mode_funcs;
ret = hibmc_de_init(priv);
if (ret) {
drm_err(dev, "failed to init de: %d\n", ret);
return ret;
}
/*
* If the serdes reg is readable and is not equal to 0,
* DP block exists and initializes it.
*/
ret = readl(priv->mmio + HIBMC_DP_HOST_SERDES_CTRL);
if (ret) {
ret = hibmc_dp_init(priv);
if (ret)
drm_err(dev, "failed to init dp: %d\n", ret);
}
ret = hibmc_vdac_init(priv);
if (ret) {
drm_err(dev, "failed to init vdac: %d\n", ret);
return ret;
}
drm_for_each_encoder(encoder, dev)
clone_mask |= drm_encoder_mask(encoder);
drm_for_each_encoder(encoder, dev)
encoder->possible_clones = clone_mask;
return 0;
}
/*
* It can operate in one of three modes: 0, 1 or Sleep.
*/
void hibmc_set_power_mode(struct hibmc_drm_private *priv, u32 power_mode)
{
u32 control_value = 0;
void __iomem *mmio = priv->mmio;
u32 input = 1;
if (power_mode > HIBMC_PW_MODE_CTL_MODE_SLEEP)
return;
if (power_mode == HIBMC_PW_MODE_CTL_MODE_SLEEP)
input = 0;
control_value = readl(mmio + HIBMC_POWER_MODE_CTRL);
control_value &= ~(HIBMC_PW_MODE_CTL_MODE_MASK |
HIBMC_PW_MODE_CTL_OSC_INPUT_MASK);
control_value |= HIBMC_FIELD(HIBMC_PW_MODE_CTL_MODE, power_mode);
control_value |= HIBMC_FIELD(HIBMC_PW_MODE_CTL_OSC_INPUT, input);
writel(control_value, mmio + HIBMC_POWER_MODE_CTRL);
}
void hibmc_set_current_gate(struct hibmc_drm_private *priv, unsigned int gate)
{
u32 gate_reg;
u32 mode;
void __iomem *mmio = priv->mmio;
/* Get current power mode. */
mode = (readl(mmio + HIBMC_POWER_MODE_CTRL) &
HIBMC_PW_MODE_CTL_MODE_MASK) >> HIBMC_PW_MODE_CTL_MODE_SHIFT;
switch (mode) {
case HIBMC_PW_MODE_CTL_MODE_MODE0:
gate_reg = HIBMC_MODE0_GATE;
break;
case HIBMC_PW_MODE_CTL_MODE_MODE1:
gate_reg = HIBMC_MODE1_GATE;
break;
default:
gate_reg = HIBMC_MODE0_GATE;
break;
}
writel(gate, mmio + gate_reg);
}
static void hibmc_display_ctrl(struct hibmc_drm_private *priv)
{
u32 reg;
reg = readl(priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE);
reg |= HIBMC_DISPLAY_CONTROL_PANELDATE(1);
writel(reg, priv->mmio + HIBMC_DISPLAY_CONTROL_HISILE);
}
static void hibmc_hw_config(struct hibmc_drm_private *priv)
{
u32 reg;
/* On hardware reset, power mode 0 is default. */
hibmc_set_power_mode(priv, HIBMC_PW_MODE_CTL_MODE_MODE0);
/* Enable display power gate & LOCALMEM power gate*/
reg = readl(priv->mmio + HIBMC_CURRENT_GATE);
reg &= ~HIBMC_CURR_GATE_DISPLAY_MASK;
reg &= ~HIBMC_CURR_GATE_LOCALMEM_MASK;
reg |= HIBMC_CURR_GATE_DISPLAY(1);
reg |= HIBMC_CURR_GATE_LOCALMEM(1);
hibmc_set_current_gate(priv, reg);
/*
* Reset the memory controller. If the memory controller
* is not reset in chip,the system might hang when sw accesses
* the memory.The memory should be resetted after
* changing the MXCLK.
*/
reg = readl(priv->mmio + HIBMC_MISC_CTRL);
reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK;
reg |= HIBMC_MSCCTL_LOCALMEM_RESET(0);
writel(reg, priv->mmio + HIBMC_MISC_CTRL);
reg &= ~HIBMC_MSCCTL_LOCALMEM_RESET_MASK;
reg |= HIBMC_MSCCTL_LOCALMEM_RESET(1);
writel(reg, priv->mmio + HIBMC_MISC_CTRL);
hibmc_display_ctrl(priv);
}
static int hibmc_hw_map(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct pci_dev *pdev = to_pci_dev(dev->dev);
resource_size_t ioaddr, iosize;
ioaddr = pci_resource_start(pdev, 1);
iosize = pci_resource_len(pdev, 1);
priv->mmio = devm_ioremap(dev->dev, ioaddr, iosize);
if (!priv->mmio) {
drm_err(dev, "Cannot map mmio region\n");
return -ENOMEM;
}
return 0;
}
static int hibmc_hw_init(struct hibmc_drm_private *priv)
{
int ret;
ret = hibmc_hw_map(priv);
if (ret)
return ret;
hibmc_hw_config(priv);
return 0;
}
static void hibmc_unload(struct drm_device *dev)
{
drm_atomic_helper_shutdown(dev);
}
static int hibmc_msi_init(struct drm_device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev->dev);
int valid_irq_num;
int irq;
int ret;
ret = pci_alloc_irq_vectors(pdev, HIBMC_MIN_VECTORS,
HIBMC_MAX_VECTORS, PCI_IRQ_MSI);
if (ret < 0) {
drm_err(dev, "enabling MSI failed: %d\n", ret);
return ret;
}
valid_irq_num = ret;
for (int i = 0; i < valid_irq_num; i++) {
irq = pci_irq_vector(pdev, i);
if (i)
/* PCI devices require shared interrupts. */
ret = devm_request_threaded_irq(&pdev->dev, irq,
hibmc_dp_interrupt,
hibmc_dp_hpd_isr,
IRQF_SHARED, g_irqs_names_map[i], dev);
else
ret = devm_request_irq(&pdev->dev, irq, hibmc_interrupt,
IRQF_SHARED, g_irqs_names_map[i], dev);
if (ret) {
drm_err(dev, "install irq failed: %d\n", ret);
return ret;
}
}
return 0;
}
static int hibmc_load(struct drm_device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev->dev);
struct hibmc_drm_private *priv = to_hibmc_drm_private(dev);
int ret;
ret = hibmc_hw_init(priv);
if (ret)
return ret;
ret = drmm_vram_helper_init(dev, pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0));
if (ret) {
drm_err(dev, "Error initializing VRAM MM; %d\n", ret);
return ret;
}
ret = hibmc_kms_init(priv);
if (ret)
goto err;
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret) {
drm_err(dev, "failed to initialize vblank: %d\n", ret);
goto err;
}
ret = hibmc_msi_init(dev);
if (ret) {
drm_err(dev, "hibmc msi init failed, ret:%d\n", ret);
goto err;
}
/* reset all the states of crtc/plane/encoder/connector */
drm_mode_config_reset(dev);
drmm_kms_helper_poll_init(dev);
return 0;
err:
hibmc_unload(dev);
drm_err(dev, "failed to initialize drm driver: %d\n", ret);
return ret;
}
static int hibmc_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct hibmc_drm_private *priv;
struct drm_device *dev;
int ret;
ret = aperture_remove_conflicting_pci_devices(pdev, hibmc_driver.name);
if (ret)
return ret;
priv = devm_drm_dev_alloc(&pdev->dev, &hibmc_driver,
struct hibmc_drm_private, dev);
if (IS_ERR(priv)) {
DRM_ERROR("failed to allocate drm_device\n");
return PTR_ERR(priv);
}
dev = &priv->dev;
pci_set_drvdata(pdev, dev);
ret = pcim_enable_device(pdev);
if (ret) {
drm_err(dev, "failed to enable pci device: %d\n", ret);
goto err_return;
}
pci_set_master(pdev);
ret = hibmc_load(dev);
if (ret) {
drm_err(dev, "failed to load hibmc: %d\n", ret);
goto err_return;
}
ret = drm_dev_register(dev, 0);
if (ret) {
drm_err(dev, "failed to register drv for userspace access: %d\n",
ret);
goto err_unload;
}
drm_client_setup(dev, NULL);
return 0;
err_unload:
hibmc_unload(dev);
err_return:
return ret;
}
static void hibmc_pci_remove(struct pci_dev *pdev)
{
struct drm_device *dev = pci_get_drvdata(pdev);
drm_dev_unregister(dev);
hibmc_unload(dev);
}
static void hibmc_pci_shutdown(struct pci_dev *pdev)
{
hibmc_pci_remove(pdev);
}
static const struct pci_device_id hibmc_pci_table[] = {
{ PCI_VDEVICE(HUAWEI, 0x1711) },
{0,}
};
static struct pci_driver hibmc_pci_driver = {
.name = "hibmc-drm",
.id_table = hibmc_pci_table,
.probe = hibmc_pci_probe,
.remove = hibmc_pci_remove,
.shutdown = hibmc_pci_shutdown,
.driver.pm = &hibmc_pm_ops,
};
drm_module_pci_driver(hibmc_pci_driver);
MODULE_DEVICE_TABLE(pci, hibmc_pci_table);
MODULE_AUTHOR("RongrongZou <zourongrong@huawei.com>");
MODULE_DESCRIPTION("DRM Driver for Hisilicon Hibmc");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Rongrong Zou <zourongrong@huawei.com>
* Rongrong Zou <zourongrong@gmail.com>
* Jianhua Li <lijianhua@huawei.com>
*/
#ifndef HIBMC_DRM_DRV_H
#define HIBMC_DRM_DRV_H
#include <linux/gpio/consumer.h>
#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>
#include <drm/drm_framebuffer.h>
#include "dp/dp_hw.h"
#define HIBMC_MIN_VECTORS 1
#define HIBMC_MAX_VECTORS 2
struct hibmc_vdac {
struct drm_device *dev;
struct drm_encoder encoder;
struct drm_connector connector;
struct i2c_adapter adapter;
struct i2c_algo_bit_data bit_data;
int phys_status;
};
struct hibmc_drm_private {
/* hw */
void __iomem *mmio;
/* drm */
struct drm_device dev;
struct drm_plane primary_plane;
struct drm_crtc crtc;
struct hibmc_vdac vdac;
struct hibmc_dp dp;
};
static inline struct hibmc_vdac *to_hibmc_vdac(struct drm_connector *connector)
{
return container_of(connector, struct hibmc_vdac, connector);
}
static inline struct hibmc_dp *to_hibmc_dp(struct drm_connector *connector)
{
return container_of(connector, struct hibmc_dp, connector);
}
static inline struct hibmc_drm_private *to_hibmc_drm_private(struct drm_device *dev)
{
return container_of(dev, struct hibmc_drm_private, dev);
}
void hibmc_set_power_mode(struct hibmc_drm_private *priv,
u32 power_mode);
void hibmc_set_current_gate(struct hibmc_drm_private *priv,
u32 gate);
int hibmc_de_init(struct hibmc_drm_private *priv);
int hibmc_vdac_init(struct hibmc_drm_private *priv);
int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *connector);
void hibmc_ddc_del(struct hibmc_vdac *vdac);
int hibmc_dp_init(struct hibmc_drm_private *priv);
void hibmc_debugfs_init(struct drm_connector *connector, struct dentry *root);
irqreturn_t hibmc_dp_hpd_isr(int irq, void *arg);
#endif
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Tian Tao <tiantao6@hisilicon.com>
*/
#include <linux/delay.h>
#include <linux/pci.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_probe_helper.h>
#include "hibmc_drm_drv.h"
#define GPIO_DATA 0x0802A0
#define GPIO_DATA_DIRECTION 0x0802A4
#define I2C_SCL_MASK BIT(0)
#define I2C_SDA_MASK BIT(1)
static void hibmc_set_i2c_signal(void *data, u32 mask, int value)
{
struct hibmc_vdac *vdac = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev);
u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION);
if (value) {
tmp_dir &= ~mask;
writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION);
} else {
u32 tmp_data = readl(priv->mmio + GPIO_DATA);
tmp_data &= ~mask;
writel(tmp_data, priv->mmio + GPIO_DATA);
tmp_dir |= mask;
writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION);
}
}
static int hibmc_get_i2c_signal(void *data, u32 mask)
{
struct hibmc_vdac *vdac = data;
struct hibmc_drm_private *priv = to_hibmc_drm_private(vdac->connector.dev);
u32 tmp_dir = readl(priv->mmio + GPIO_DATA_DIRECTION);
if ((tmp_dir & mask) != mask) {
tmp_dir &= ~mask;
writel(tmp_dir, priv->mmio + GPIO_DATA_DIRECTION);
}
return (readl(priv->mmio + GPIO_DATA) & mask) ? 1 : 0;
}
static void hibmc_ddc_setsda(void *data, int state)
{
hibmc_set_i2c_signal(data, I2C_SDA_MASK, state);
}
static void hibmc_ddc_setscl(void *data, int state)
{
hibmc_set_i2c_signal(data, I2C_SCL_MASK, state);
}
static int hibmc_ddc_getsda(void *data)
{
return hibmc_get_i2c_signal(data, I2C_SDA_MASK);
}
static int hibmc_ddc_getscl(void *data)
{
return hibmc_get_i2c_signal(data, I2C_SCL_MASK);
}
int hibmc_ddc_create(struct drm_device *drm_dev, struct hibmc_vdac *vdac)
{
vdac->adapter.owner = THIS_MODULE;
snprintf(vdac->adapter.name, I2C_NAME_SIZE, "HIS i2c bit bus");
vdac->adapter.dev.parent = drm_dev->dev;
i2c_set_adapdata(&vdac->adapter, vdac);
vdac->adapter.algo_data = &vdac->bit_data;
vdac->bit_data.udelay = 20;
vdac->bit_data.timeout = usecs_to_jiffies(2000);
vdac->bit_data.data = vdac;
vdac->bit_data.setsda = hibmc_ddc_setsda;
vdac->bit_data.setscl = hibmc_ddc_setscl;
vdac->bit_data.getsda = hibmc_ddc_getsda;
vdac->bit_data.getscl = hibmc_ddc_getscl;
return i2c_bit_add_bus(&vdac->adapter);
}
void hibmc_ddc_del(struct hibmc_vdac *vdac)
{
i2c_del_adapter(&vdac->adapter);
}
@@ -0,0 +1,204 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Rongrong Zou <zourongrong@huawei.com>
* Rongrong Zou <zourongrong@gmail.com>
* Jianhua Li <lijianhua@huawei.com>
*/
#ifndef HIBMC_DRM_HW_H
#define HIBMC_DRM_HW_H
/* register definition */
#define HIBMC_MISC_CTRL 0x4
#define HIBMC_MSCCTL_LOCALMEM_RESET(x) ((x) << 6)
#define HIBMC_MSCCTL_LOCALMEM_RESET_MASK 0x40
#define HIBMC_CURRENT_GATE 0x000040
#define HIBMC_CURR_GATE_DISPLAY(x) ((x) << 2)
#define HIBMC_CURR_GATE_DISPLAY_MASK 0x4
#define HIBMC_CURR_GATE_LOCALMEM(x) ((x) << 1)
#define HIBMC_CURR_GATE_LOCALMEM_MASK 0x2
#define HIBMC_MODE0_GATE 0x000044
#define HIBMC_MODE1_GATE 0x000048
#define HIBMC_POWER_MODE_CTRL 0x00004C
#define HIBMC_PW_MODE_CTL_OSC_INPUT(x) ((x) << 3)
#define HIBMC_PW_MODE_CTL_OSC_INPUT_MASK 0x8
#define HIBMC_PW_MODE_CTL_MODE(x) ((x) << 0)
#define HIBMC_PW_MODE_CTL_MODE_MASK 0x03
#define HIBMC_PW_MODE_CTL_MODE_SHIFT 0
#define HIBMC_PW_MODE_CTL_MODE_MODE0 0
#define HIBMC_PW_MODE_CTL_MODE_MODE1 1
#define HIBMC_PW_MODE_CTL_MODE_SLEEP 2
#define HIBMC_PANEL_PLL_CTRL 0x00005C
#define HIBMC_CRT_PLL_CTRL 0x000060
#define HIBMC_PLL_CTRL_BYPASS(x) ((x) << 18)
#define HIBMC_PLL_CTRL_BYPASS_MASK 0x40000
#define HIBMC_PLL_CTRL_POWER(x) ((x) << 17)
#define HIBMC_PLL_CTRL_POWER_MASK 0x20000
#define HIBMC_PLL_CTRL_INPUT(x) ((x) << 16)
#define HIBMC_PLL_CTRL_INPUT_MASK 0x10000
#define HIBMC_PLL_CTRL_POD(x) ((x) << 14)
#define HIBMC_PLL_CTRL_POD_MASK 0xC000
#define HIBMC_PLL_CTRL_OD(x) ((x) << 12)
#define HIBMC_PLL_CTRL_OD_MASK 0x3000
#define HIBMC_PLL_CTRL_N(x) ((x) << 8)
#define HIBMC_PLL_CTRL_N_MASK 0xF00
#define HIBMC_PLL_CTRL_M(x) ((x) << 0)
#define HIBMC_PLL_CTRL_M_MASK 0xFF
#define HIBMC_CRT_DISP_CTL 0x80200
#define HIBMC_CRT_DISP_CTL_DPMS(x) ((x) << 30)
#define HIBMC_CRT_DISP_CTL_DPMS_MASK 0xc0000000
#define HIBMC_CRT_DPMS_ON 0
#define HIBMC_CRT_DPMS_OFF 3
#define HIBMC_CRT_DISP_CTL_CRTSELECT(x) ((x) << 25)
#define HIBMC_CRT_DISP_CTL_CRTSELECT_MASK 0x2000000
#define HIBMC_CRTSELECT_CRT 1
#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE(x) ((x) << 14)
#define HIBMC_CRT_DISP_CTL_CLOCK_PHASE_MASK 0x4000
#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE(x) ((x) << 13)
#define HIBMC_CRT_DISP_CTL_VSYNC_PHASE_MASK 0x2000
#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE(x) ((x) << 12)
#define HIBMC_CRT_DISP_CTL_HSYNC_PHASE_MASK 0x1000
#define HIBMC_CRT_DISP_CTL_TIMING(x) ((x) << 8)
#define HIBMC_CRT_DISP_CTL_TIMING_MASK 0x100
#define HIBMC_CTL_DISP_CTL_GAMMA(x) ((x) << 3)
#define HIBMC_CTL_DISP_CTL_GAMMA_MASK 0x08
#define HIBMC_CRT_DISP_CTL_PLANE(x) ((x) << 2)
#define HIBMC_CRT_DISP_CTL_PLANE_MASK 4
#define HIBMC_CRT_DISP_CTL_FORMAT(x) ((x) << 0)
#define HIBMC_CRT_DISP_CTL_FORMAT_MASK 0x03
#define HIBMC_CRT_FB_ADDRESS 0x080204
#define HIBMC_CRT_FB_WIDTH 0x080208
#define HIBMC_CRT_FB_WIDTH_WIDTH(x) ((x) << 16)
#define HIBMC_CRT_FB_WIDTH_WIDTH_MASK 0x3FFF0000
#define HIBMC_CRT_FB_WIDTH_OFFS(x) ((x) << 0)
#define HIBMC_CRT_FB_WIDTH_OFFS_MASK 0x3FFF
#define HIBMC_CRT_HORZ_TOTAL 0x08020C
#define HIBMC_CRT_HORZ_TOTAL_TOTAL(x) ((x) << 16)
#define HIBMC_CRT_HORZ_TOTAL_TOTAL_MASK 0xFFF0000
#define HIBMC_CRT_HORZ_TOTAL_DISP_END(x) ((x) << 0)
#define HIBMC_CRT_HORZ_TOTAL_DISP_END_MASK 0xFFF
#define HIBMC_CRT_HORZ_SYNC 0x080210
#define HIBMC_CRT_HORZ_SYNC_WIDTH(x) ((x) << 16)
#define HIBMC_CRT_HORZ_SYNC_WIDTH_MASK 0xFF0000
#define HIBMC_CRT_HORZ_SYNC_START(x) ((x) << 0)
#define HIBMC_CRT_HORZ_SYNC_START_MASK 0xFFF
#define HIBMC_CRT_VERT_TOTAL 0x080214
#define HIBMC_CRT_VERT_TOTAL_TOTAL(x) ((x) << 16)
#define HIBMC_CRT_VERT_TOTAL_TOTAL_MASK 0x7FFF0000
#define HIBMC_CRT_VERT_TOTAL_DISP_END(x) ((x) << 0)
#define HIBMC_CRT_VERT_TOTAL_DISP_END_MASK 0x7FF
#define HIBMC_CRT_VERT_SYNC 0x080218
#define HIBMC_CRT_VERT_SYNC_HEIGHT(x) ((x) << 16)
#define HIBMC_CRT_VERT_SYNC_HEIGHT_MASK 0x3F0000
#define HIBMC_CRT_VERT_SYNC_START(x) ((x) << 0)
#define HIBMC_CRT_VERT_SYNC_START_MASK 0x7FF
/* Auto Centering */
#define HIBMC_CRT_AUTO_CENTERING_TL 0x080280
#define HIBMC_CRT_AUTO_CENTERING_TL_TOP(x) ((x) << 16)
#define HIBMC_CRT_AUTO_CENTERING_TL_TOP_MASK 0x7FF0000
#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT(x) ((x) << 0)
#define HIBMC_CRT_AUTO_CENTERING_TL_LEFT_MASK 0x7FF
#define HIBMC_CRT_AUTO_CENTERING_BR 0x080284
#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM(x) ((x) << 16)
#define HIBMC_CRT_AUTO_CENTERING_BR_BOTTOM_MASK 0x7FF0000
#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT(x) ((x) << 0)
#define HIBMC_CRT_AUTO_CENTERING_BR_RIGHT_MASK 0x7FF
/* register to control panel output */
#define HIBMC_DISPLAY_CONTROL_HISILE 0x80288
#define HIBMC_DISPLAY_CONTROL_FPVDDEN(x) ((x) << 0)
#define HIBMC_DISPLAY_CONTROL_PANELDATE(x) ((x) << 1)
#define HIBMC_DISPLAY_CONTROL_FPEN(x) ((x) << 2)
#define HIBMC_DISPLAY_CONTROL_VBIASEN(x) ((x) << 3)
#define HIBMC_RAW_INTERRUPT 0x80290
#define HIBMC_RAW_INTERRUPT_VBLANK(x) ((x) << 2)
#define HIBMC_RAW_INTERRUPT_VBLANK_MASK 0x4
#define HIBMC_RAW_INTERRUPT_EN 0x80298
#define HIBMC_RAW_INTERRUPT_EN_VBLANK(x) ((x) << 2)
#define HIBMC_RAW_INTERRUPT_EN_VBLANK_MASK 0x4
/* register and values for PLL control */
#define CRT_PLL1_HS 0x802a8
#define CRT_PLL1_HS_OUTER_BYPASS(x) ((x) << 30)
#define CRT_PLL1_HS_INTER_BYPASS(x) ((x) << 29)
#define CRT_PLL1_HS_POWERON(x) ((x) << 24)
#define CRT_PLL1_HS_25MHZ 0x23d40f02
#define CRT_PLL1_HS_40MHZ 0x23940801
#define CRT_PLL1_HS_65MHZ 0x23940d01
#define CRT_PLL1_HS_78MHZ 0x23540F82
#define CRT_PLL1_HS_74MHZ 0x23941dc2
#define CRT_PLL1_HS_80MHZ 0x23941001
#define CRT_PLL1_HS_80MHZ_1152 0x23540fc2
#define CRT_PLL1_HS_106MHZ 0x237C1641
#define CRT_PLL1_HS_108MHZ 0x23b41b01
#define CRT_PLL1_HS_162MHZ 0x23480681
#define CRT_PLL1_HS_148MHZ 0x23541dc2
#define CRT_PLL1_HS_193MHZ 0x234807c1
#define CRT_PLL2_HS 0x802ac
#define CRT_PLL2_HS_25MHZ 0x206B851E
#define CRT_PLL2_HS_40MHZ 0x30000000
#define CRT_PLL2_HS_65MHZ 0x40000000
#define CRT_PLL2_HS_78MHZ 0x50E147AE
#define CRT_PLL2_HS_74MHZ 0x602B6AE7
#define CRT_PLL2_HS_80MHZ 0x70000000
#define CRT_PLL2_HS_106MHZ 0x0075c28f
#define CRT_PLL2_HS_108MHZ 0x80000000
#define CRT_PLL2_HS_162MHZ 0xA0000000
#define CRT_PLL2_HS_148MHZ 0xB0CCCCCD
#define CRT_PLL2_HS_193MHZ 0xC0872B02
#define HIBMC_CRT_PALETTE 0x80C00
#define HIBMC_FIELD(field, value) (field(value) & field##_MASK)
#endif
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* Hisilicon Hibmc SoC drm driver
*
* Based on the bochs drm driver.
*
* Copyright (c) 2016 Huawei Limited.
*
* Author:
* Rongrong Zou <zourongrong@huawei.com>
* Rongrong Zou <zourongrong@gmail.com>
* Jianhua Li <lijianhua@huawei.com>
*/
#include <linux/io.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_simple_kms_helper.h>
#include "hibmc_drm_drv.h"
#include "hibmc_drm_regs.h"
static int hibmc_connector_get_modes(struct drm_connector *connector)
{
struct drm_mode_config *mode_config = &connector->dev->mode_config;
struct hibmc_vdac *vdac = to_hibmc_vdac(connector);
int count;
if (vdac->phys_status == connector_status_connected) {
count = drm_connector_helper_get_modes(connector);
} else {
drm_edid_connector_update(connector, NULL);
count = drm_add_modes_noedid(connector,
mode_config->max_width,
mode_config->max_height);
if (count)
drm_set_preferred_mode(connector, 1024, 768);
}
return count;
}
static void hibmc_connector_destroy(struct drm_connector *connector)
{
struct hibmc_vdac *vdac = to_hibmc_vdac(connector);
hibmc_ddc_del(vdac);
drm_connector_cleanup(connector);
}
static int hibmc_vdac_detect(struct drm_connector *connector,
struct drm_modeset_acquire_ctx *ctx,
bool force)
{
struct hibmc_drm_private *priv = to_hibmc_drm_private(connector->dev);
int status = drm_connector_helper_detect_from_ddc(connector, ctx,
force);
struct hibmc_vdac *vdac = to_hibmc_vdac(connector);
if (priv->dp.phys_status == connector_status_connected) {
vdac->phys_status = status;
return status;
}
if (status != vdac->phys_status)
++connector->epoch_counter;
vdac->phys_status = status;
/* When both the DP and VDAC physical status are disconnected,
* the "connected" status is returned to support KVM display.
*/
return connector_status_connected;
}
static const struct drm_connector_helper_funcs
hibmc_connector_helper_funcs = {
.get_modes = hibmc_connector_get_modes,
.detect_ctx = hibmc_vdac_detect,
};
static const struct drm_connector_funcs hibmc_connector_funcs = {
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = hibmc_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
int hibmc_vdac_init(struct hibmc_drm_private *priv)
{
struct drm_device *dev = &priv->dev;
struct hibmc_vdac *vdac = &priv->vdac;
struct drm_encoder *encoder = &vdac->encoder;
struct drm_crtc *crtc = &priv->crtc;
struct drm_connector *connector = &vdac->connector;
int ret;
ret = hibmc_ddc_create(dev, vdac);
if (ret) {
drm_err(dev, "failed to create ddc: %d\n", ret);
return ret;
}
encoder->possible_crtcs = drm_crtc_mask(crtc);
ret = drmm_encoder_init(dev, encoder, NULL, DRM_MODE_ENCODER_DAC, NULL);
if (ret) {
drm_err(dev, "failed to init encoder: %d\n", ret);
goto err;
}
ret = drm_connector_init_with_ddc(dev, connector,
&hibmc_connector_funcs,
DRM_MODE_CONNECTOR_VGA,
&vdac->adapter);
if (ret) {
drm_err(dev, "failed to init connector: %d\n", ret);
goto err;
}
drm_connector_helper_add(connector, &hibmc_connector_helper_funcs);
drm_connector_attach_encoder(connector, encoder);
connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
vdac->phys_status = connector_status_disconnected;
return 0;
err:
hibmc_ddc_del(vdac);
return ret;
}