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,7 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# hisilicon drm device configuration.
# Please keep this list sorted alphabetically
source "drivers/gpu/drm/hisilicon/hibmc/Kconfig"
source "drivers/gpu/drm/hisilicon/kirin/Kconfig"
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for hisilicon drm drivers.
# Please keep this list sorted alphabetically
obj-$(CONFIG_DRM_HISI_HIBMC) += hibmc/
obj-$(CONFIG_DRM_HISI_KIRIN) += kirin/
@@ -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;
}
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_HISI_KIRIN
tristate "DRM Support for Hisilicon Kirin series SoCs Platform"
depends on DRM && OF && (ARM64 || COMPILE_TEST)
select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_GEM_DMA_HELPER
select DRM_MIPI_DSI
help
Choose this option if you have a hisilicon Kirin chipsets(hi6220).
If M is selected the module will be called kirin-drm.
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
kirin-drm-y := kirin_drm_drv.o \
kirin_drm_ade.o
obj-$(CONFIG_DRM_HISI_KIRIN) += kirin-drm.o dw_drm_dsi.o
@@ -0,0 +1,901 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* DesignWare MIPI DSI Host Controller v1.02 driver
*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*
* Author:
* Xinliang Liu <z.liuxinliang@hisilicon.com>
* Xinliang Liu <xinliang.liu@linaro.org>
* Xinwei Kong <kong.kongxinwei@hisilicon.com>
*/
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_device.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include "dw_dsi_reg.h"
#define MAX_TX_ESC_CLK 10
#define ROUND(x, y) ((x) / (y) + \
((x) % (y) * 10 / (y) >= 5 ? 1 : 0))
#define PHY_REF_CLK_RATE 19200000
#define PHY_REF_CLK_PERIOD_PS (1000000000 / (PHY_REF_CLK_RATE / 1000))
#define encoder_to_dsi(encoder) \
container_of(encoder, struct dw_dsi, encoder)
#define host_to_dsi(host) \
container_of(host, struct dw_dsi, host)
struct mipi_phy_params {
u32 clk_t_lpx;
u32 clk_t_hs_prepare;
u32 clk_t_hs_zero;
u32 clk_t_hs_trial;
u32 clk_t_wakeup;
u32 data_t_lpx;
u32 data_t_hs_prepare;
u32 data_t_hs_zero;
u32 data_t_hs_trial;
u32 data_t_ta_go;
u32 data_t_ta_get;
u32 data_t_wakeup;
u32 hstx_ckg_sel;
u32 pll_fbd_div5f;
u32 pll_fbd_div1f;
u32 pll_fbd_2p;
u32 pll_enbwt;
u32 pll_fbd_p;
u32 pll_fbd_s;
u32 pll_pre_div1p;
u32 pll_pre_p;
u32 pll_vco_750M;
u32 pll_lpf_rs;
u32 pll_lpf_cs;
u32 clklp2hs_time;
u32 clkhs2lp_time;
u32 lp2hs_time;
u32 hs2lp_time;
u32 clk_to_data_delay;
u32 data_to_clk_delay;
u32 lane_byte_clk_kHz;
u32 clk_division;
};
struct dsi_hw_ctx {
void __iomem *base;
struct clk *pclk;
};
struct dw_dsi {
struct drm_encoder encoder;
struct device *dev;
struct mipi_dsi_host host;
struct drm_display_mode cur_mode;
struct dsi_hw_ctx *ctx;
struct mipi_phy_params phy;
u32 lanes;
enum mipi_dsi_pixel_format format;
unsigned long mode_flags;
bool enable;
};
struct dsi_data {
struct dw_dsi dsi;
struct dsi_hw_ctx ctx;
};
struct dsi_phy_range {
u32 min_range_kHz;
u32 max_range_kHz;
u32 pll_vco_750M;
u32 hstx_ckg_sel;
};
static const struct dsi_phy_range dphy_range_info[] = {
{ 46875, 62500, 1, 7 },
{ 62500, 93750, 0, 7 },
{ 93750, 125000, 1, 6 },
{ 125000, 187500, 0, 6 },
{ 187500, 250000, 1, 5 },
{ 250000, 375000, 0, 5 },
{ 375000, 500000, 1, 4 },
{ 500000, 750000, 0, 4 },
{ 750000, 1000000, 1, 0 },
{ 1000000, 1500000, 0, 0 }
};
static u32 dsi_calc_phy_rate(u32 req_kHz, struct mipi_phy_params *phy)
{
u32 ref_clk_ps = PHY_REF_CLK_PERIOD_PS;
u32 tmp_kHz = req_kHz;
u32 i = 0;
u32 q_pll = 1;
u32 m_pll = 0;
u32 n_pll = 0;
u32 r_pll = 1;
u32 m_n = 0;
u32 m_n_int = 0;
u32 f_kHz = 0;
u64 temp;
/*
* Find a rate >= req_kHz.
*/
do {
f_kHz = tmp_kHz;
for (i = 0; i < ARRAY_SIZE(dphy_range_info); i++)
if (f_kHz >= dphy_range_info[i].min_range_kHz &&
f_kHz <= dphy_range_info[i].max_range_kHz)
break;
if (i == ARRAY_SIZE(dphy_range_info)) {
DRM_ERROR("%dkHz out of range\n", f_kHz);
return 0;
}
phy->pll_vco_750M = dphy_range_info[i].pll_vco_750M;
phy->hstx_ckg_sel = dphy_range_info[i].hstx_ckg_sel;
if (phy->hstx_ckg_sel <= 7 &&
phy->hstx_ckg_sel >= 4)
q_pll = 0x10 >> (7 - phy->hstx_ckg_sel);
temp = f_kHz * (u64)q_pll * (u64)ref_clk_ps;
m_n_int = div64_u64_rem(temp, 1000000000, &temp);
m_n = div_u64(temp, 100000000);
if (m_n_int % 2 == 0) {
if (m_n * 6 >= 50) {
n_pll = 2;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 3;
m_pll = m_n_int * n_pll + 2;
} else {
n_pll = 1;
m_pll = m_n_int * n_pll;
}
} else {
if (m_n * 6 >= 50) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 30) {
n_pll = 1;
m_pll = (m_n_int + 1) * n_pll;
} else if (m_n * 6 >= 10) {
n_pll = 3;
m_pll = m_n_int * n_pll + 1;
} else {
n_pll = 2;
m_pll = m_n_int * n_pll;
}
}
if (n_pll == 1) {
phy->pll_fbd_p = 0;
phy->pll_pre_div1p = 1;
} else {
phy->pll_fbd_p = n_pll;
phy->pll_pre_div1p = 0;
}
if (phy->pll_fbd_2p <= 7 && phy->pll_fbd_2p >= 4)
r_pll = 0x10 >> (7 - phy->pll_fbd_2p);
if (m_pll == 2) {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
} else if (m_pll >= 2 * 2 * r_pll && m_pll <= 2 * 4 * r_pll) {
phy->pll_pre_p = m_pll / (2 * r_pll);
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 1;
phy->pll_fbd_div5f = 0;
} else if (m_pll >= 2 * 5 * r_pll && m_pll <= 2 * 150 * r_pll) {
if (((m_pll / (2 * r_pll)) % 2) == 0) {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2 - 1;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2 + 2;
} else {
phy->pll_pre_p =
(m_pll / (2 * r_pll)) / 2;
phy->pll_fbd_s =
(m_pll / (2 * r_pll)) % 2;
}
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 0;
} else {
phy->pll_pre_p = 0;
phy->pll_fbd_s = 0;
phy->pll_fbd_div1f = 0;
phy->pll_fbd_div5f = 1;
}
f_kHz = div64_u64((u64)1000000000 * (u64)m_pll,
(u64)ref_clk_ps * (u64)n_pll * (u64)q_pll);
if (f_kHz >= req_kHz)
break;
tmp_kHz += 10;
} while (true);
return f_kHz;
}
static void dsi_get_phy_params(u32 phy_req_kHz,
struct mipi_phy_params *phy)
{
u32 ref_clk_ps = PHY_REF_CLK_PERIOD_PS;
u32 phy_rate_kHz;
u32 ui;
memset(phy, 0, sizeof(*phy));
phy_rate_kHz = dsi_calc_phy_rate(phy_req_kHz, phy);
if (!phy_rate_kHz)
return;
ui = 1000000 / phy_rate_kHz;
phy->clk_t_lpx = ROUND(50, 8 * ui);
phy->clk_t_hs_prepare = ROUND(133, 16 * ui) - 1;
phy->clk_t_hs_zero = ROUND(262, 8 * ui);
phy->clk_t_hs_trial = 2 * (ROUND(60, 8 * ui) - 1);
phy->clk_t_wakeup = ROUND(1000000, (ref_clk_ps / 1000) - 1);
if (phy->clk_t_wakeup > 0xff)
phy->clk_t_wakeup = 0xff;
phy->data_t_wakeup = phy->clk_t_wakeup;
phy->data_t_lpx = phy->clk_t_lpx;
phy->data_t_hs_prepare = ROUND(125 + 10 * ui, 16 * ui) - 1;
phy->data_t_hs_zero = ROUND(105 + 6 * ui, 8 * ui);
phy->data_t_hs_trial = 2 * (ROUND(60 + 4 * ui, 8 * ui) - 1);
phy->data_t_ta_go = 3;
phy->data_t_ta_get = 4;
phy->pll_enbwt = 1;
phy->clklp2hs_time = ROUND(407, 8 * ui) + 12;
phy->clkhs2lp_time = ROUND(105 + 12 * ui, 8 * ui);
phy->lp2hs_time = ROUND(240 + 12 * ui, 8 * ui) + 1;
phy->hs2lp_time = phy->clkhs2lp_time;
phy->clk_to_data_delay = 1 + phy->clklp2hs_time;
phy->data_to_clk_delay = ROUND(60 + 52 * ui, 8 * ui) +
phy->clkhs2lp_time;
phy->lane_byte_clk_kHz = phy_rate_kHz / 8;
phy->clk_division =
DIV_ROUND_UP(phy->lane_byte_clk_kHz, MAX_TX_ESC_CLK);
}
static u32 dsi_get_dpi_color_coding(enum mipi_dsi_pixel_format format)
{
u32 val;
/*
* TODO: only support RGB888 now, to support more
*/
switch (format) {
case MIPI_DSI_FMT_RGB888:
val = DSI_24BITS_1;
break;
default:
val = DSI_24BITS_1;
break;
}
return val;
}
/*
* dsi phy reg write function
*/
static void dsi_phy_tst_set(void __iomem *base, u32 reg, u32 val)
{
u32 reg_write = 0x10000 + reg;
/*
* latch reg first
*/
writel(reg_write, base + PHY_TST_CTRL1);
writel(0x02, base + PHY_TST_CTRL0);
writel(0x00, base + PHY_TST_CTRL0);
/*
* then latch value
*/
writel(val, base + PHY_TST_CTRL1);
writel(0x02, base + PHY_TST_CTRL0);
writel(0x00, base + PHY_TST_CTRL0);
}
static void dsi_set_phy_timer(void __iomem *base,
struct mipi_phy_params *phy,
u32 lanes)
{
u32 val;
/*
* Set lane value and phy stop wait time.
*/
val = (lanes - 1) | (PHY_STOP_WAIT_TIME << 8);
writel(val, base + PHY_IF_CFG);
/*
* Set phy clk division.
*/
val = readl(base + CLKMGR_CFG) | phy->clk_division;
writel(val, base + CLKMGR_CFG);
/*
* Set lp and hs switching params.
*/
dw_update_bits(base + PHY_TMR_CFG, 24, MASK(8), phy->hs2lp_time);
dw_update_bits(base + PHY_TMR_CFG, 16, MASK(8), phy->lp2hs_time);
dw_update_bits(base + PHY_TMR_LPCLK_CFG, 16, MASK(10),
phy->clkhs2lp_time);
dw_update_bits(base + PHY_TMR_LPCLK_CFG, 0, MASK(10),
phy->clklp2hs_time);
dw_update_bits(base + CLK_DATA_TMR_CFG, 8, MASK(8),
phy->data_to_clk_delay);
dw_update_bits(base + CLK_DATA_TMR_CFG, 0, MASK(8),
phy->clk_to_data_delay);
}
static void dsi_set_mipi_phy(void __iomem *base,
struct mipi_phy_params *phy,
u32 lanes)
{
u32 delay_count;
u32 val;
u32 i;
/* phy timer setting */
dsi_set_phy_timer(base, phy, lanes);
/*
* Reset to clean up phy tst params.
*/
writel(0, base + PHY_RSTZ);
writel(0, base + PHY_TST_CTRL0);
writel(1, base + PHY_TST_CTRL0);
writel(0, base + PHY_TST_CTRL0);
/*
* Clock lane timing control setting: TLPX, THS-PREPARE,
* THS-ZERO, THS-TRAIL, TWAKEUP.
*/
dsi_phy_tst_set(base, CLK_TLPX, phy->clk_t_lpx);
dsi_phy_tst_set(base, CLK_THS_PREPARE, phy->clk_t_hs_prepare);
dsi_phy_tst_set(base, CLK_THS_ZERO, phy->clk_t_hs_zero);
dsi_phy_tst_set(base, CLK_THS_TRAIL, phy->clk_t_hs_trial);
dsi_phy_tst_set(base, CLK_TWAKEUP, phy->clk_t_wakeup);
/*
* Data lane timing control setting: TLPX, THS-PREPARE,
* THS-ZERO, THS-TRAIL, TTA-GO, TTA-GET, TWAKEUP.
*/
for (i = 0; i < lanes; i++) {
dsi_phy_tst_set(base, DATA_TLPX(i), phy->data_t_lpx);
dsi_phy_tst_set(base, DATA_THS_PREPARE(i),
phy->data_t_hs_prepare);
dsi_phy_tst_set(base, DATA_THS_ZERO(i), phy->data_t_hs_zero);
dsi_phy_tst_set(base, DATA_THS_TRAIL(i), phy->data_t_hs_trial);
dsi_phy_tst_set(base, DATA_TTA_GO(i), phy->data_t_ta_go);
dsi_phy_tst_set(base, DATA_TTA_GET(i), phy->data_t_ta_get);
dsi_phy_tst_set(base, DATA_TWAKEUP(i), phy->data_t_wakeup);
}
/*
* physical configuration: I, pll I, pll II, pll III,
* pll IV, pll V.
*/
dsi_phy_tst_set(base, PHY_CFG_I, phy->hstx_ckg_sel);
val = (phy->pll_fbd_div5f << 5) + (phy->pll_fbd_div1f << 4) +
(phy->pll_fbd_2p << 1) + phy->pll_enbwt;
dsi_phy_tst_set(base, PHY_CFG_PLL_I, val);
dsi_phy_tst_set(base, PHY_CFG_PLL_II, phy->pll_fbd_p);
dsi_phy_tst_set(base, PHY_CFG_PLL_III, phy->pll_fbd_s);
val = (phy->pll_pre_div1p << 7) + phy->pll_pre_p;
dsi_phy_tst_set(base, PHY_CFG_PLL_IV, val);
val = (5 << 5) + (phy->pll_vco_750M << 4) + (phy->pll_lpf_rs << 2) +
phy->pll_lpf_cs;
dsi_phy_tst_set(base, PHY_CFG_PLL_V, val);
writel(PHY_ENABLECLK, base + PHY_RSTZ);
udelay(1);
writel(PHY_ENABLECLK | PHY_UNSHUTDOWNZ, base + PHY_RSTZ);
udelay(1);
writel(PHY_ENABLECLK | PHY_UNRSTZ | PHY_UNSHUTDOWNZ, base + PHY_RSTZ);
usleep_range(1000, 1500);
/*
* wait for phy's clock ready
*/
delay_count = 100;
while (delay_count) {
val = readl(base + PHY_STATUS);
if ((BIT(0) | BIT(2)) & val)
break;
udelay(1);
delay_count--;
}
if (!delay_count)
DRM_INFO("phylock and phystopstateclklane is not ready.\n");
}
static void dsi_set_mode_timing(void __iomem *base,
u32 lane_byte_clk_kHz,
struct drm_display_mode *mode,
enum mipi_dsi_pixel_format format)
{
u32 hfp, hbp, hsw, vfp, vbp, vsw;
u32 hline_time;
u32 hsa_time;
u32 hbp_time;
u32 pixel_clk_kHz;
int htot, vtot;
u32 val;
u64 tmp;
val = dsi_get_dpi_color_coding(format);
writel(val, base + DPI_COLOR_CODING);
val = (mode->flags & DRM_MODE_FLAG_NHSYNC ? 1 : 0) << 2;
val |= (mode->flags & DRM_MODE_FLAG_NVSYNC ? 1 : 0) << 1;
writel(val, base + DPI_CFG_POL);
/*
* The DSI IP accepts vertical timing using lines as normal,
* but horizontal timing is a mixture of pixel-clocks for the
* active region and byte-lane clocks for the blanking-related
* timings. hfp is specified as the total hline_time in byte-
* lane clocks minus hsa, hbp and active.
*/
pixel_clk_kHz = mode->clock;
htot = mode->htotal;
vtot = mode->vtotal;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
hsw = mode->hsync_end - mode->hsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
vsw = mode->vsync_end - mode->vsync_start;
if (vsw > 15) {
DRM_DEBUG_DRIVER("vsw exceeded 15\n");
vsw = 15;
}
hsa_time = (hsw * lane_byte_clk_kHz) / pixel_clk_kHz;
hbp_time = (hbp * lane_byte_clk_kHz) / pixel_clk_kHz;
tmp = (u64)htot * (u64)lane_byte_clk_kHz;
hline_time = DIV_ROUND_UP_ULL(tmp, pixel_clk_kHz);
/* all specified in byte-lane clocks */
writel(hsa_time, base + VID_HSA_TIME);
writel(hbp_time, base + VID_HBP_TIME);
writel(hline_time, base + VID_HLINE_TIME);
writel(vsw, base + VID_VSA_LINES);
writel(vbp, base + VID_VBP_LINES);
writel(vfp, base + VID_VFP_LINES);
writel(mode->vdisplay, base + VID_VACTIVE_LINES);
writel(mode->hdisplay, base + VID_PKT_SIZE);
DRM_DEBUG_DRIVER("htot=%d, hfp=%d, hbp=%d, hsw=%d\n",
htot, hfp, hbp, hsw);
DRM_DEBUG_DRIVER("vtol=%d, vfp=%d, vbp=%d, vsw=%d\n",
vtot, vfp, vbp, vsw);
DRM_DEBUG_DRIVER("hsa_time=%d, hbp_time=%d, hline_time=%d\n",
hsa_time, hbp_time, hline_time);
}
static void dsi_set_video_mode(void __iomem *base, unsigned long flags)
{
u32 val;
u32 mode_mask = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
u32 non_burst_sync_pulse = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
u32 non_burst_sync_event = MIPI_DSI_MODE_VIDEO;
/*
* choose video mode type
*/
if ((flags & mode_mask) == non_burst_sync_pulse)
val = DSI_NON_BURST_SYNC_PULSES;
else if ((flags & mode_mask) == non_burst_sync_event)
val = DSI_NON_BURST_SYNC_EVENTS;
else
val = DSI_BURST_SYNC_PULSES_1;
writel(val, base + VID_MODE_CFG);
writel(PHY_TXREQUESTCLKHS, base + LPCLK_CTRL);
writel(DSI_VIDEO_MODE, base + MODE_CFG);
}
static void dsi_mipi_init(struct dw_dsi *dsi)
{
struct dsi_hw_ctx *ctx = dsi->ctx;
struct mipi_phy_params *phy = &dsi->phy;
struct drm_display_mode *mode = &dsi->cur_mode;
u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
void __iomem *base = ctx->base;
u32 dphy_req_kHz;
/*
* count phy params
*/
dphy_req_kHz = mode->clock * bpp / dsi->lanes;
dsi_get_phy_params(dphy_req_kHz, phy);
/* reset Core */
writel(RESET, base + PWR_UP);
/* set dsi phy params */
dsi_set_mipi_phy(base, phy, dsi->lanes);
/* set dsi mode timing */
dsi_set_mode_timing(base, phy->lane_byte_clk_kHz, mode, dsi->format);
/* set dsi video mode */
dsi_set_video_mode(base, dsi->mode_flags);
/* dsi wake up */
writel(POWERUP, base + PWR_UP);
DRM_DEBUG_DRIVER("lanes=%d, pixel_clk=%d kHz, bytes_freq=%d kHz\n",
dsi->lanes, mode->clock, phy->lane_byte_clk_kHz);
}
static void dsi_encoder_disable(struct drm_encoder *encoder)
{
struct dw_dsi *dsi = encoder_to_dsi(encoder);
struct dsi_hw_ctx *ctx = dsi->ctx;
void __iomem *base = ctx->base;
if (!dsi->enable)
return;
writel(0, base + PWR_UP);
writel(0, base + LPCLK_CTRL);
writel(0, base + PHY_RSTZ);
clk_disable_unprepare(ctx->pclk);
dsi->enable = false;
}
static void dsi_encoder_enable(struct drm_encoder *encoder)
{
struct dw_dsi *dsi = encoder_to_dsi(encoder);
struct dsi_hw_ctx *ctx = dsi->ctx;
int ret;
if (dsi->enable)
return;
ret = clk_prepare_enable(ctx->pclk);
if (ret) {
DRM_ERROR("fail to enable pclk: %d\n", ret);
return;
}
dsi_mipi_init(dsi);
dsi->enable = true;
}
static enum drm_mode_status dsi_encoder_phy_mode_valid(
struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
struct dw_dsi *dsi = encoder_to_dsi(encoder);
struct mipi_phy_params phy;
u32 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
u32 req_kHz, act_kHz, lane_byte_clk_kHz;
/* Calculate the lane byte clk using the adjusted mode clk */
memset(&phy, 0, sizeof(phy));
req_kHz = mode->clock * bpp / dsi->lanes;
act_kHz = dsi_calc_phy_rate(req_kHz, &phy);
lane_byte_clk_kHz = act_kHz / 8;
DRM_DEBUG_DRIVER("Checking mode %ix%i-%i@%i clock: %i...",
mode->hdisplay, mode->vdisplay, bpp,
drm_mode_vrefresh(mode), mode->clock);
/*
* Make sure the adjusted mode clock and the lane byte clk
* have a common denominator base frequency
*/
if (mode->clock/dsi->lanes == lane_byte_clk_kHz/3) {
DRM_DEBUG_DRIVER("OK!\n");
return MODE_OK;
}
DRM_DEBUG_DRIVER("BAD!\n");
return MODE_BAD;
}
static enum drm_mode_status dsi_encoder_mode_valid(struct drm_encoder *encoder,
const struct drm_display_mode *mode)
{
const struct drm_crtc_helper_funcs *crtc_funcs = NULL;
struct drm_crtc *crtc = NULL;
struct drm_display_mode adj_mode;
enum drm_mode_status ret;
/*
* The crtc might adjust the mode, so go through the
* possible crtcs (technically just one) and call
* mode_fixup to figure out the adjusted mode before we
* validate it.
*/
drm_for_each_crtc(crtc, encoder->dev) {
/*
* reset adj_mode to the mode value each time,
* so we don't adjust the mode twice
*/
drm_mode_init(&adj_mode, mode);
crtc_funcs = crtc->helper_private;
if (crtc_funcs && crtc_funcs->mode_fixup)
if (!crtc_funcs->mode_fixup(crtc, mode, &adj_mode))
return MODE_BAD;
ret = dsi_encoder_phy_mode_valid(encoder, &adj_mode);
if (ret != MODE_OK)
return ret;
}
return MODE_OK;
}
static void dsi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
struct dw_dsi *dsi = encoder_to_dsi(encoder);
drm_mode_copy(&dsi->cur_mode, adj_mode);
}
static int dsi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
/* do nothing */
return 0;
}
static const struct drm_encoder_helper_funcs dw_encoder_helper_funcs = {
.atomic_check = dsi_encoder_atomic_check,
.mode_valid = dsi_encoder_mode_valid,
.mode_set = dsi_encoder_mode_set,
.enable = dsi_encoder_enable,
.disable = dsi_encoder_disable
};
static int dw_drm_encoder_init(struct device *dev,
struct drm_device *drm_dev,
struct drm_encoder *encoder)
{
int ret;
u32 crtc_mask = drm_of_find_possible_crtcs(drm_dev, dev->of_node);
if (!crtc_mask) {
DRM_ERROR("failed to find crtc mask\n");
return -EINVAL;
}
encoder->possible_crtcs = crtc_mask;
ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
if (ret) {
DRM_ERROR("failed to init dsi encoder\n");
return ret;
}
drm_encoder_helper_add(encoder, &dw_encoder_helper_funcs);
return 0;
}
static const struct component_ops dsi_ops;
static int dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *mdsi)
{
struct dw_dsi *dsi = host_to_dsi(host);
struct device *dev = host->dev;
int ret;
if (mdsi->lanes < 1 || mdsi->lanes > 4) {
DRM_ERROR("dsi device params invalid\n");
return -EINVAL;
}
dsi->lanes = mdsi->lanes;
dsi->format = mdsi->format;
dsi->mode_flags = mdsi->mode_flags;
ret = component_add(dev, &dsi_ops);
if (ret)
return ret;
return 0;
}
static int dsi_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *mdsi)
{
struct device *dev = host->dev;
component_del(dev, &dsi_ops);
return 0;
}
static const struct mipi_dsi_host_ops dsi_host_ops = {
.attach = dsi_host_attach,
.detach = dsi_host_detach,
};
static int dsi_host_init(struct device *dev, struct dw_dsi *dsi)
{
struct mipi_dsi_host *host = &dsi->host;
int ret;
host->dev = dev;
host->ops = &dsi_host_ops;
ret = mipi_dsi_host_register(host);
if (ret) {
DRM_ERROR("failed to register dsi host\n");
return ret;
}
return 0;
}
static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
{
struct drm_encoder *encoder = &dsi->encoder;
struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
struct device_node *np = dsi->dev->of_node;
/*
* Get the endpoint node. In our case, dsi has one output port1
* to which the external HDMI bridge is connected.
*/
bridge = of_drm_get_bridge_by_endpoint(np, 1, 0);
if (IS_ERR(bridge))
return PTR_ERR(bridge);
/* associate the bridge to dsi encoder */
return drm_bridge_attach(encoder, bridge, NULL, 0);
}
static int dsi_bind(struct device *dev, struct device *master, void *data)
{
struct dsi_data *ddata = dev_get_drvdata(dev);
struct dw_dsi *dsi = &ddata->dsi;
struct drm_device *drm_dev = data;
int ret;
ret = dw_drm_encoder_init(dev, drm_dev, &dsi->encoder);
if (ret)
return ret;
ret = dsi_bridge_init(drm_dev, dsi);
if (ret)
return ret;
return 0;
}
static void dsi_unbind(struct device *dev, struct device *master, void *data)
{
/* do nothing */
}
static const struct component_ops dsi_ops = {
.bind = dsi_bind,
.unbind = dsi_unbind,
};
static int dsi_parse_dt(struct platform_device *pdev, struct dw_dsi *dsi)
{
struct dsi_hw_ctx *ctx = dsi->ctx;
ctx->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(ctx->pclk)) {
DRM_ERROR("failed to get pclk clock\n");
return PTR_ERR(ctx->pclk);
}
ctx->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctx->base)) {
DRM_ERROR("failed to remap dsi io region\n");
return PTR_ERR(ctx->base);
}
return 0;
}
static int dsi_probe(struct platform_device *pdev)
{
struct dsi_data *data;
struct dw_dsi *dsi;
struct dsi_hw_ctx *ctx;
int ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
DRM_ERROR("failed to allocate dsi data.\n");
return -ENOMEM;
}
dsi = &data->dsi;
ctx = &data->ctx;
dsi->ctx = ctx;
dsi->dev = &pdev->dev;
ret = dsi_parse_dt(pdev, dsi);
if (ret)
return ret;
platform_set_drvdata(pdev, data);
ret = dsi_host_init(&pdev->dev, dsi);
if (ret)
return ret;
return 0;
}
static void dsi_remove(struct platform_device *pdev)
{
struct dsi_data *data = platform_get_drvdata(pdev);
struct dw_dsi *dsi = &data->dsi;
mipi_dsi_host_unregister(&dsi->host);
}
static const struct of_device_id dsi_of_match[] = {
{.compatible = "hisilicon,hi6220-dsi"},
{ }
};
MODULE_DEVICE_TABLE(of, dsi_of_match);
static struct platform_driver dsi_driver = {
.probe = dsi_probe,
.remove = dsi_remove,
.driver = {
.name = "dw-dsi",
.of_match_table = dsi_of_match,
},
};
module_platform_driver(dsi_driver);
MODULE_AUTHOR("Xinliang Liu <xinliang.liu@linaro.org>");
MODULE_AUTHOR("Xinliang Liu <z.liuxinliang@hisilicon.com>");
MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
MODULE_DESCRIPTION("DesignWare MIPI DSI Host Controller v1.02 driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*/
#ifndef __DW_DSI_REG_H__
#define __DW_DSI_REG_H__
#include <linux/io.h>
#define MASK(x) (BIT(x) - 1)
/*
* regs
*/
#define PWR_UP 0x04 /* Core power-up */
#define RESET 0
#define POWERUP BIT(0)
#define PHY_IF_CFG 0xA4 /* D-PHY interface configuration */
#define CLKMGR_CFG 0x08 /* the internal clock dividers */
#define PHY_RSTZ 0xA0 /* D-PHY reset control */
#define PHY_ENABLECLK BIT(2)
#define PHY_UNRSTZ BIT(1)
#define PHY_UNSHUTDOWNZ BIT(0)
#define PHY_TST_CTRL0 0xB4 /* D-PHY test interface control 0 */
#define PHY_TST_CTRL1 0xB8 /* D-PHY test interface control 1 */
#define CLK_TLPX 0x10
#define CLK_THS_PREPARE 0x11
#define CLK_THS_ZERO 0x12
#define CLK_THS_TRAIL 0x13
#define CLK_TWAKEUP 0x14
#define DATA_TLPX(x) (0x20 + ((x) << 4))
#define DATA_THS_PREPARE(x) (0x21 + ((x) << 4))
#define DATA_THS_ZERO(x) (0x22 + ((x) << 4))
#define DATA_THS_TRAIL(x) (0x23 + ((x) << 4))
#define DATA_TTA_GO(x) (0x24 + ((x) << 4))
#define DATA_TTA_GET(x) (0x25 + ((x) << 4))
#define DATA_TWAKEUP(x) (0x26 + ((x) << 4))
#define PHY_CFG_I 0x60
#define PHY_CFG_PLL_I 0x63
#define PHY_CFG_PLL_II 0x64
#define PHY_CFG_PLL_III 0x65
#define PHY_CFG_PLL_IV 0x66
#define PHY_CFG_PLL_V 0x67
#define DPI_COLOR_CODING 0x10 /* DPI color coding */
#define DPI_CFG_POL 0x14 /* DPI polarity configuration */
#define VID_HSA_TIME 0x48 /* Horizontal Sync Active time */
#define VID_HBP_TIME 0x4C /* Horizontal Back Porch time */
#define VID_HLINE_TIME 0x50 /* Line time */
#define VID_VSA_LINES 0x54 /* Vertical Sync Active period */
#define VID_VBP_LINES 0x58 /* Vertical Back Porch period */
#define VID_VFP_LINES 0x5C /* Vertical Front Porch period */
#define VID_VACTIVE_LINES 0x60 /* Vertical resolution */
#define VID_PKT_SIZE 0x3C /* Video packet size */
#define VID_MODE_CFG 0x38 /* Video mode configuration */
#define PHY_TMR_CFG 0x9C /* Data lanes timing configuration */
#define BTA_TO_CNT 0x8C /* Response timeout definition */
#define PHY_TMR_LPCLK_CFG 0x98 /* clock lane timing configuration */
#define CLK_DATA_TMR_CFG 0xCC
#define LPCLK_CTRL 0x94 /* Low-power in clock lane */
#define PHY_TXREQUESTCLKHS BIT(0)
#define MODE_CFG 0x34 /* Video or Command mode selection */
#define PHY_STATUS 0xB0 /* D-PHY PPI status interface */
#define PHY_STOP_WAIT_TIME 0x30
/*
* regs relevant enum
*/
enum dpi_color_coding {
DSI_24BITS_1 = 5,
};
enum dsi_video_mode_type {
DSI_NON_BURST_SYNC_PULSES = 0,
DSI_NON_BURST_SYNC_EVENTS,
DSI_BURST_SYNC_PULSES_1,
DSI_BURST_SYNC_PULSES_2
};
enum dsi_work_mode {
DSI_VIDEO_MODE = 0,
DSI_COMMAND_MODE
};
/*
* Register Write/Read Helper functions
*/
static inline void dw_update_bits(void __iomem *addr, u32 bit_start,
u32 mask, u32 val)
{
u32 tmp, orig;
orig = readl(addr);
tmp = orig & ~(mask << bit_start);
tmp |= (val & mask) << bit_start;
writel(tmp, addr);
}
#endif /* __DW_DRM_DSI_H__ */
@@ -0,0 +1,226 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*/
#ifndef __KIRIN_ADE_REG_H__
#define __KIRIN_ADE_REG_H__
/*
* ADE Registers
*/
#define MASK(x) (BIT_ULL(x) - 1)
#define ADE_CTRL 0x0004
#define FRM_END_START_OFST 0
#define FRM_END_START_MASK MASK(2)
#define AUTO_CLK_GATE_EN_OFST 0
#define AUTO_CLK_GATE_EN BIT(0)
#define ADE_DISP_SRC_CFG 0x0018
#define ADE_CTRL1 0x008C
#define ADE_EN 0x0100
#define ADE_DISABLE 0
#define ADE_ENABLE 1
/* reset and reload regs */
#define ADE_SOFT_RST_SEL(x) (0x0078 + (x) * 0x4)
#define ADE_RELOAD_DIS(x) (0x00AC + (x) * 0x4)
#define RDMA_OFST 0
#define CLIP_OFST 15
#define SCL_OFST 21
#define CTRAN_OFST 24
#define OVLY_OFST 37 /* 32+5 */
/* channel regs */
#define RD_CH_CTRL(x) (0x1004 + (x) * 0x80)
#define RD_CH_ADDR(x) (0x1008 + (x) * 0x80)
#define RD_CH_SIZE(x) (0x100C + (x) * 0x80)
#define RD_CH_STRIDE(x) (0x1010 + (x) * 0x80)
#define RD_CH_SPACE(x) (0x1014 + (x) * 0x80)
#define RD_CH_EN(x) (0x1020 + (x) * 0x80)
/* overlay regs */
#define ADE_OVLY1_TRANS_CFG 0x002C
#define ADE_OVLY_CTL 0x0098
#define ADE_OVLY_CH_XY0(x) (0x2004 + (x) * 4)
#define ADE_OVLY_CH_XY1(x) (0x2024 + (x) * 4)
#define ADE_OVLY_CH_CTL(x) (0x204C + (x) * 4)
#define ADE_OVLY_OUTPUT_SIZE(x) (0x2070 + (x) * 8)
#define OUTPUT_XSIZE_OFST 16
#define ADE_OVLYX_CTL(x) (0x209C + (x) * 4)
#define CH_OVLY_SEL_OFST(x) ((x) * 4)
#define CH_OVLY_SEL_MASK MASK(2)
#define CH_OVLY_SEL_VAL(x) ((x) + 1)
#define CH_ALP_MODE_OFST 0
#define CH_ALP_SEL_OFST 2
#define CH_UNDER_ALP_SEL_OFST 4
#define CH_EN_OFST 6
#define CH_ALP_GBL_OFST 15
#define CH_SEL_OFST 28
/* ctran regs */
#define ADE_CTRAN_DIS(x) (0x5004 + (x) * 0x100)
#define CTRAN_BYPASS_ON 1
#define CTRAN_BYPASS_OFF 0
#define ADE_CTRAN_IMAGE_SIZE(x) (0x503C + (x) * 0x100)
/* clip regs */
#define ADE_CLIP_DISABLE(x) (0x6800 + (x) * 0x100)
#define ADE_CLIP_SIZE0(x) (0x6804 + (x) * 0x100)
#define ADE_CLIP_SIZE1(x) (0x6808 + (x) * 0x100)
/*
* LDI Registers
*/
#define LDI_HRZ_CTRL0 0x7400
#define HBP_OFST 20
#define LDI_HRZ_CTRL1 0x7404
#define LDI_VRT_CTRL0 0x7408
#define VBP_OFST 20
#define LDI_VRT_CTRL1 0x740C
#define LDI_PLR_CTRL 0x7410
#define FLAG_NVSYNC BIT(0)
#define FLAG_NHSYNC BIT(1)
#define FLAG_NPIXCLK BIT(2)
#define FLAG_NDE BIT(3)
#define LDI_DSP_SIZE 0x7414
#define VSIZE_OFST 20
#define LDI_INT_EN 0x741C
#define FRAME_END_INT_EN_OFST 1
#define LDI_CTRL 0x7420
#define BPP_OFST 3
#define DATA_GATE_EN BIT(2)
#define LDI_EN BIT(0)
#define LDI_MSK_INT 0x7428
#define LDI_INT_CLR 0x742C
#define LDI_WORK_MODE 0x7430
#define LDI_HDMI_DSI_GT 0x7434
/*
* ADE media bus service regs
*/
#define ADE0_QOSGENERATOR_MODE 0x010C
#define QOSGENERATOR_MODE_MASK MASK(2)
#define ADE0_QOSGENERATOR_EXTCONTROL 0x0118
#define SOCKET_QOS_EN BIT(0)
#define ADE1_QOSGENERATOR_MODE 0x020C
#define ADE1_QOSGENERATOR_EXTCONTROL 0x0218
/*
* ADE regs relevant enums
*/
enum frame_end_start {
/* regs take effect in every vsync */
REG_EFFECTIVE_IN_VSYNC = 0,
/* regs take effect in fist ade en and every frame end */
REG_EFFECTIVE_IN_ADEEN_FRMEND,
/* regs take effect in ade en immediately */
REG_EFFECTIVE_IN_ADEEN,
/* regs take effect in first vsync and every frame end */
REG_EFFECTIVE_IN_VSYNC_FRMEND
};
enum ade_fb_format {
ADE_RGB_565 = 0,
ADE_BGR_565,
ADE_XRGB_8888,
ADE_XBGR_8888,
ADE_ARGB_8888,
ADE_ABGR_8888,
ADE_RGBA_8888,
ADE_BGRA_8888,
ADE_RGB_888,
ADE_BGR_888 = 9,
ADE_FORMAT_UNSUPPORT = 800
};
enum ade_channel {
ADE_CH1 = 0, /* channel 1 for primary plane */
ADE_CH_NUM
};
enum ade_scale {
ADE_SCL1 = 0,
ADE_SCL2,
ADE_SCL3,
ADE_SCL_NUM
};
enum ade_ctran {
ADE_CTRAN1 = 0,
ADE_CTRAN2,
ADE_CTRAN3,
ADE_CTRAN4,
ADE_CTRAN5,
ADE_CTRAN6,
ADE_CTRAN_NUM
};
enum ade_overlay {
ADE_OVLY1 = 0,
ADE_OVLY2,
ADE_OVLY3,
ADE_OVLY_NUM
};
enum ade_alpha_mode {
ADE_ALP_GLOBAL = 0,
ADE_ALP_PIXEL,
ADE_ALP_PIXEL_AND_GLB
};
enum ade_alpha_blending_mode {
ADE_ALP_MUL_COEFF_0 = 0, /* alpha */
ADE_ALP_MUL_COEFF_1, /* 1-alpha */
ADE_ALP_MUL_COEFF_2, /* 0 */
ADE_ALP_MUL_COEFF_3 /* 1 */
};
/*
* LDI regs relevant enums
*/
enum dsi_pclk_en {
DSI_PCLK_ON = 0,
DSI_PCLK_OFF
};
enum ldi_output_format {
LDI_OUT_RGB_565 = 0,
LDI_OUT_RGB_666,
LDI_OUT_RGB_888
};
enum ldi_work_mode {
TEST_MODE = 0,
NORMAL_MODE
};
enum ldi_input_source {
DISP_SRC_NONE = 0,
DISP_SRC_OVLY2,
DISP_SRC_DISP,
DISP_SRC_ROT,
DISP_SRC_SCL2
};
/*
* ADE media bus service relevant enums
*/
enum qos_generator_mode {
FIXED_MODE = 0,
LIMITER_MODE,
BYPASS_MODE,
REGULATOR_MODE
};
/*
* Register Write/Read Helper functions
*/
static inline void ade_update_bits(void __iomem *addr, u32 bit_start,
u32 mask, u32 val)
{
u32 tmp, orig;
orig = readl(addr);
tmp = orig & ~(mask << bit_start);
tmp |= (val & mask) << bit_start;
writel(tmp, addr);
}
#endif
@@ -0,0 +1,951 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Hisilicon Hi6220 SoC ADE(Advanced Display Engine)'s crtc&plane driver
*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*
* Author:
* Xinliang Liu <z.liuxinliang@hisilicon.com>
* Xinliang Liu <xinliang.liu@linaro.org>
* Xinwei Kong <kong.kongxinwei@hisilicon.com>
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <video/display_timing.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_drv.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_fbdev_dma.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include "kirin_drm_drv.h"
#include "kirin_ade_reg.h"
#define OUT_OVLY ADE_OVLY2 /* output overlay compositor */
#define ADE_DEBUG 1
struct ade_hw_ctx {
void __iomem *base;
struct regmap *noc_regmap;
struct clk *ade_core_clk;
struct clk *media_noc_clk;
struct clk *ade_pix_clk;
struct reset_control *reset;
bool power_on;
int irq;
struct drm_crtc *crtc;
};
static const struct kirin_format ade_formats[] = {
/* 16bpp RGB: */
{ DRM_FORMAT_RGB565, ADE_RGB_565 },
{ DRM_FORMAT_BGR565, ADE_BGR_565 },
/* 24bpp RGB: */
{ DRM_FORMAT_RGB888, ADE_RGB_888 },
{ DRM_FORMAT_BGR888, ADE_BGR_888 },
/* 32bpp [A]RGB: */
{ DRM_FORMAT_XRGB8888, ADE_XRGB_8888 },
{ DRM_FORMAT_XBGR8888, ADE_XBGR_8888 },
{ DRM_FORMAT_RGBA8888, ADE_RGBA_8888 },
{ DRM_FORMAT_BGRA8888, ADE_BGRA_8888 },
{ DRM_FORMAT_ARGB8888, ADE_ARGB_8888 },
{ DRM_FORMAT_ABGR8888, ADE_ABGR_8888 },
};
static const u32 channel_formats[] = {
/* channel 1,2,3,4 */
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
};
/* convert from fourcc format to ade format */
static u32 ade_get_format(u32 pixel_format)
{
int i;
for (i = 0; i < ARRAY_SIZE(ade_formats); i++)
if (ade_formats[i].pixel_format == pixel_format)
return ade_formats[i].hw_format;
/* not found */
DRM_ERROR("Not found pixel format!!fourcc_format= %d\n",
pixel_format);
return ADE_FORMAT_UNSUPPORT;
}
static void ade_update_reload_bit(void __iomem *base, u32 bit_num, u32 val)
{
u32 bit_ofst, reg_num;
bit_ofst = bit_num % 32;
reg_num = bit_num / 32;
ade_update_bits(base + ADE_RELOAD_DIS(reg_num), bit_ofst,
MASK(1), !!val);
}
static u32 ade_read_reload_bit(void __iomem *base, u32 bit_num)
{
u32 tmp, bit_ofst, reg_num;
bit_ofst = bit_num % 32;
reg_num = bit_num / 32;
tmp = readl(base + ADE_RELOAD_DIS(reg_num));
return !!(BIT(bit_ofst) & tmp);
}
static void ade_init(struct ade_hw_ctx *ctx)
{
void __iomem *base = ctx->base;
/* enable clk gate */
ade_update_bits(base + ADE_CTRL1, AUTO_CLK_GATE_EN_OFST,
AUTO_CLK_GATE_EN, ADE_ENABLE);
/* clear overlay */
writel(0, base + ADE_OVLY1_TRANS_CFG);
writel(0, base + ADE_OVLY_CTL);
writel(0, base + ADE_OVLYX_CTL(OUT_OVLY));
/* clear reset and reload regs */
writel(MASK(32), base + ADE_SOFT_RST_SEL(0));
writel(MASK(32), base + ADE_SOFT_RST_SEL(1));
writel(MASK(32), base + ADE_RELOAD_DIS(0));
writel(MASK(32), base + ADE_RELOAD_DIS(1));
/*
* for video mode, all the ade registers should
* become effective at frame end.
*/
ade_update_bits(base + ADE_CTRL, FRM_END_START_OFST,
FRM_END_START_MASK, REG_EFFECTIVE_IN_ADEEN_FRMEND);
}
static bool ade_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
adjusted_mode->clock =
clk_round_rate(ctx->ade_pix_clk, mode->clock * 1000) / 1000;
return true;
}
static void ade_set_pix_clk(struct ade_hw_ctx *ctx,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
u32 clk_Hz = mode->clock * 1000;
int ret;
/*
* Success should be guaranteed in mode_valid call back,
* so failure shouldn't happen here
*/
ret = clk_set_rate(ctx->ade_pix_clk, clk_Hz);
if (ret)
DRM_ERROR("failed to set pixel clk %dHz (%d)\n", clk_Hz, ret);
adj_mode->clock = clk_get_rate(ctx->ade_pix_clk) / 1000;
}
static void ade_ldi_set_mode(struct ade_hw_ctx *ctx,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
void __iomem *base = ctx->base;
u32 width = mode->hdisplay;
u32 height = mode->vdisplay;
u32 hfp, hbp, hsw, vfp, vbp, vsw;
u32 plr_flags;
plr_flags = (mode->flags & DRM_MODE_FLAG_NVSYNC) ? FLAG_NVSYNC : 0;
plr_flags |= (mode->flags & DRM_MODE_FLAG_NHSYNC) ? FLAG_NHSYNC : 0;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
hsw = mode->hsync_end - mode->hsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
vsw = mode->vsync_end - mode->vsync_start;
if (vsw > 15) {
DRM_DEBUG_DRIVER("vsw exceeded 15\n");
vsw = 15;
}
writel((hbp << HBP_OFST) | hfp, base + LDI_HRZ_CTRL0);
/* the configured value is actual value - 1 */
writel(hsw - 1, base + LDI_HRZ_CTRL1);
writel((vbp << VBP_OFST) | vfp, base + LDI_VRT_CTRL0);
/* the configured value is actual value - 1 */
writel(vsw - 1, base + LDI_VRT_CTRL1);
/* the configured value is actual value - 1 */
writel(((height - 1) << VSIZE_OFST) | (width - 1),
base + LDI_DSP_SIZE);
writel(plr_flags, base + LDI_PLR_CTRL);
/* set overlay compositor output size */
writel(((width - 1) << OUTPUT_XSIZE_OFST) | (height - 1),
base + ADE_OVLY_OUTPUT_SIZE(OUT_OVLY));
/* ctran6 setting */
writel(CTRAN_BYPASS_ON, base + ADE_CTRAN_DIS(ADE_CTRAN6));
/* the configured value is actual value - 1 */
writel(width * height - 1, base + ADE_CTRAN_IMAGE_SIZE(ADE_CTRAN6));
ade_update_reload_bit(base, CTRAN_OFST + ADE_CTRAN6, 0);
ade_set_pix_clk(ctx, mode, adj_mode);
DRM_DEBUG_DRIVER("set mode: %dx%d\n", width, height);
}
static int ade_power_up(struct ade_hw_ctx *ctx)
{
int ret;
ret = clk_prepare_enable(ctx->media_noc_clk);
if (ret) {
DRM_ERROR("failed to enable media_noc_clk (%d)\n", ret);
return ret;
}
ret = reset_control_deassert(ctx->reset);
if (ret) {
DRM_ERROR("failed to deassert reset\n");
return ret;
}
ret = clk_prepare_enable(ctx->ade_core_clk);
if (ret) {
DRM_ERROR("failed to enable ade_core_clk (%d)\n", ret);
return ret;
}
ade_init(ctx);
ctx->power_on = true;
return 0;
}
static void ade_power_down(struct ade_hw_ctx *ctx)
{
void __iomem *base = ctx->base;
writel(ADE_DISABLE, base + LDI_CTRL);
/* dsi pixel off */
writel(DSI_PCLK_OFF, base + LDI_HDMI_DSI_GT);
clk_disable_unprepare(ctx->ade_core_clk);
reset_control_assert(ctx->reset);
clk_disable_unprepare(ctx->media_noc_clk);
ctx->power_on = false;
}
static void ade_set_medianoc_qos(struct ade_hw_ctx *ctx)
{
struct regmap *map = ctx->noc_regmap;
regmap_update_bits(map, ADE0_QOSGENERATOR_MODE,
QOSGENERATOR_MODE_MASK, BYPASS_MODE);
regmap_update_bits(map, ADE0_QOSGENERATOR_EXTCONTROL,
SOCKET_QOS_EN, SOCKET_QOS_EN);
regmap_update_bits(map, ADE1_QOSGENERATOR_MODE,
QOSGENERATOR_MODE_MASK, BYPASS_MODE);
regmap_update_bits(map, ADE1_QOSGENERATOR_EXTCONTROL,
SOCKET_QOS_EN, SOCKET_QOS_EN);
}
static int ade_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
void __iomem *base = ctx->base;
if (!ctx->power_on)
(void)ade_power_up(ctx);
ade_update_bits(base + LDI_INT_EN, FRAME_END_INT_EN_OFST,
MASK(1), 1);
return 0;
}
static void ade_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
void __iomem *base = ctx->base;
if (!ctx->power_on) {
DRM_ERROR("power is down! vblank disable fail\n");
return;
}
ade_update_bits(base + LDI_INT_EN, FRAME_END_INT_EN_OFST,
MASK(1), 0);
}
static irqreturn_t ade_irq_handler(int irq, void *data)
{
struct ade_hw_ctx *ctx = data;
struct drm_crtc *crtc = ctx->crtc;
void __iomem *base = ctx->base;
u32 status;
status = readl(base + LDI_MSK_INT);
DRM_DEBUG_VBL("LDI IRQ: status=0x%X\n", status);
/* vblank irq */
if (status & BIT(FRAME_END_INT_EN_OFST)) {
ade_update_bits(base + LDI_INT_CLR, FRAME_END_INT_EN_OFST,
MASK(1), 1);
drm_crtc_handle_vblank(crtc);
}
return IRQ_HANDLED;
}
static void ade_display_enable(struct ade_hw_ctx *ctx)
{
void __iomem *base = ctx->base;
u32 out_fmt = LDI_OUT_RGB_888;
/* enable output overlay compositor */
writel(ADE_ENABLE, base + ADE_OVLYX_CTL(OUT_OVLY));
ade_update_reload_bit(base, OVLY_OFST + OUT_OVLY, 0);
/* display source setting */
writel(DISP_SRC_OVLY2, base + ADE_DISP_SRC_CFG);
/* enable ade */
writel(ADE_ENABLE, base + ADE_EN);
/* enable ldi */
writel(NORMAL_MODE, base + LDI_WORK_MODE);
writel((out_fmt << BPP_OFST) | DATA_GATE_EN | LDI_EN,
base + LDI_CTRL);
/* dsi pixel on */
writel(DSI_PCLK_ON, base + LDI_HDMI_DSI_GT);
}
#if ADE_DEBUG
static void ade_rdma_dump_regs(void __iomem *base, u32 ch)
{
u32 reg_ctrl, reg_addr, reg_size, reg_stride, reg_space, reg_en;
u32 val;
reg_ctrl = RD_CH_CTRL(ch);
reg_addr = RD_CH_ADDR(ch);
reg_size = RD_CH_SIZE(ch);
reg_stride = RD_CH_STRIDE(ch);
reg_space = RD_CH_SPACE(ch);
reg_en = RD_CH_EN(ch);
val = ade_read_reload_bit(base, RDMA_OFST + ch);
DRM_DEBUG_DRIVER("[rdma%d]: reload(%d)\n", ch + 1, val);
val = readl(base + reg_ctrl);
DRM_DEBUG_DRIVER("[rdma%d]: reg_ctrl(0x%08x)\n", ch + 1, val);
val = readl(base + reg_addr);
DRM_DEBUG_DRIVER("[rdma%d]: reg_addr(0x%08x)\n", ch + 1, val);
val = readl(base + reg_size);
DRM_DEBUG_DRIVER("[rdma%d]: reg_size(0x%08x)\n", ch + 1, val);
val = readl(base + reg_stride);
DRM_DEBUG_DRIVER("[rdma%d]: reg_stride(0x%08x)\n", ch + 1, val);
val = readl(base + reg_space);
DRM_DEBUG_DRIVER("[rdma%d]: reg_space(0x%08x)\n", ch + 1, val);
val = readl(base + reg_en);
DRM_DEBUG_DRIVER("[rdma%d]: reg_en(0x%08x)\n", ch + 1, val);
}
static void ade_clip_dump_regs(void __iomem *base, u32 ch)
{
u32 val;
val = ade_read_reload_bit(base, CLIP_OFST + ch);
DRM_DEBUG_DRIVER("[clip%d]: reload(%d)\n", ch + 1, val);
val = readl(base + ADE_CLIP_DISABLE(ch));
DRM_DEBUG_DRIVER("[clip%d]: reg_clip_disable(0x%08x)\n", ch + 1, val);
val = readl(base + ADE_CLIP_SIZE0(ch));
DRM_DEBUG_DRIVER("[clip%d]: reg_clip_size0(0x%08x)\n", ch + 1, val);
val = readl(base + ADE_CLIP_SIZE1(ch));
DRM_DEBUG_DRIVER("[clip%d]: reg_clip_size1(0x%08x)\n", ch + 1, val);
}
static void ade_compositor_routing_dump_regs(void __iomem *base, u32 ch)
{
u8 ovly_ch = 0; /* TODO: Only primary plane now */
u32 val;
val = readl(base + ADE_OVLY_CH_XY0(ovly_ch));
DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_xy0(0x%08x)\n", ovly_ch, val);
val = readl(base + ADE_OVLY_CH_XY1(ovly_ch));
DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_xy1(0x%08x)\n", ovly_ch, val);
val = readl(base + ADE_OVLY_CH_CTL(ovly_ch));
DRM_DEBUG_DRIVER("[overlay ch%d]: reg_ch_ctl(0x%08x)\n", ovly_ch, val);
}
static void ade_dump_overlay_compositor_regs(void __iomem *base, u32 comp)
{
u32 val;
val = ade_read_reload_bit(base, OVLY_OFST + comp);
DRM_DEBUG_DRIVER("[overlay%d]: reload(%d)\n", comp + 1, val);
writel(ADE_ENABLE, base + ADE_OVLYX_CTL(comp));
DRM_DEBUG_DRIVER("[overlay%d]: reg_ctl(0x%08x)\n", comp + 1, val);
val = readl(base + ADE_OVLY_CTL);
DRM_DEBUG_DRIVER("ovly_ctl(0x%08x)\n", val);
}
static void ade_dump_regs(void __iomem *base)
{
u32 i;
/* dump channel regs */
for (i = 0; i < ADE_CH_NUM; i++) {
/* dump rdma regs */
ade_rdma_dump_regs(base, i);
/* dump clip regs */
ade_clip_dump_regs(base, i);
/* dump compositor routing regs */
ade_compositor_routing_dump_regs(base, i);
}
/* dump overlay compositor regs */
ade_dump_overlay_compositor_regs(base, OUT_OVLY);
}
#else
static void ade_dump_regs(void __iomem *base) { }
#endif
static void ade_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
int ret;
if (kcrtc->enable)
return;
if (!ctx->power_on) {
ret = ade_power_up(ctx);
if (ret)
return;
}
ade_set_medianoc_qos(ctx);
ade_display_enable(ctx);
ade_dump_regs(ctx->base);
drm_crtc_vblank_on(crtc);
kcrtc->enable = true;
}
static void ade_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
if (!kcrtc->enable)
return;
drm_crtc_vblank_off(crtc);
ade_power_down(ctx);
kcrtc->enable = false;
}
static void ade_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
struct drm_display_mode *mode = &crtc->state->mode;
struct drm_display_mode *adj_mode = &crtc->state->adjusted_mode;
if (!ctx->power_on)
(void)ade_power_up(ctx);
ade_ldi_set_mode(ctx, mode, adj_mode);
}
static void ade_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
struct drm_display_mode *mode = &crtc->state->mode;
struct drm_display_mode *adj_mode = &crtc->state->adjusted_mode;
if (!ctx->power_on)
(void)ade_power_up(ctx);
ade_ldi_set_mode(ctx, mode, adj_mode);
}
static void ade_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct kirin_crtc *kcrtc = to_kirin_crtc(crtc);
struct ade_hw_ctx *ctx = kcrtc->hw_ctx;
struct drm_pending_vblank_event *event = crtc->state->event;
void __iomem *base = ctx->base;
/* only crtc is enabled regs take effect */
if (kcrtc->enable) {
ade_dump_regs(base);
/* flush ade registers */
writel(ADE_ENABLE, base + ADE_EN);
}
if (event) {
crtc->state->event = NULL;
spin_lock_irq(&crtc->dev->event_lock);
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
}
}
static const struct drm_crtc_helper_funcs ade_crtc_helper_funcs = {
.mode_fixup = ade_crtc_mode_fixup,
.mode_set_nofb = ade_crtc_mode_set_nofb,
.atomic_begin = ade_crtc_atomic_begin,
.atomic_flush = ade_crtc_atomic_flush,
.atomic_enable = ade_crtc_atomic_enable,
.atomic_disable = ade_crtc_atomic_disable,
};
static const struct drm_crtc_funcs ade_crtc_funcs = {
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.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 = ade_crtc_enable_vblank,
.disable_vblank = ade_crtc_disable_vblank,
};
static void ade_rdma_set(void __iomem *base, struct drm_framebuffer *fb,
u32 ch, u32 y, u32 in_h, u32 fmt)
{
struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, 0);
u32 reg_ctrl, reg_addr, reg_size, reg_stride, reg_space, reg_en;
u32 stride = fb->pitches[0];
u32 addr = (u32) obj->dma_addr + y * stride;
DRM_DEBUG_DRIVER("rdma%d: (y=%d, height=%d), stride=%d, paddr=0x%x\n",
ch + 1, y, in_h, stride, (u32) obj->dma_addr);
DRM_DEBUG_DRIVER("addr=0x%x, fb:%dx%d, pixel_format=%d(%p4cc)\n",
addr, fb->width, fb->height, fmt,
&fb->format->format);
/* get reg offset */
reg_ctrl = RD_CH_CTRL(ch);
reg_addr = RD_CH_ADDR(ch);
reg_size = RD_CH_SIZE(ch);
reg_stride = RD_CH_STRIDE(ch);
reg_space = RD_CH_SPACE(ch);
reg_en = RD_CH_EN(ch);
/*
* TODO: set rotation
*/
writel((fmt << 16) & 0x1f0000, base + reg_ctrl);
writel(addr, base + reg_addr);
writel((in_h << 16) | stride, base + reg_size);
writel(stride, base + reg_stride);
writel(in_h * stride, base + reg_space);
writel(ADE_ENABLE, base + reg_en);
ade_update_reload_bit(base, RDMA_OFST + ch, 0);
}
static void ade_rdma_disable(void __iomem *base, u32 ch)
{
u32 reg_en;
/* get reg offset */
reg_en = RD_CH_EN(ch);
writel(0, base + reg_en);
ade_update_reload_bit(base, RDMA_OFST + ch, 1);
}
static void ade_clip_set(void __iomem *base, u32 ch, u32 fb_w, u32 x,
u32 in_w, u32 in_h)
{
u32 disable_val;
u32 clip_left;
u32 clip_right;
/*
* clip width, no need to clip height
*/
if (fb_w == in_w) { /* bypass */
disable_val = 1;
clip_left = 0;
clip_right = 0;
} else {
disable_val = 0;
clip_left = x;
clip_right = fb_w - (x + in_w) - 1;
}
DRM_DEBUG_DRIVER("clip%d: clip_left=%d, clip_right=%d\n",
ch + 1, clip_left, clip_right);
writel(disable_val, base + ADE_CLIP_DISABLE(ch));
writel((fb_w - 1) << 16 | (in_h - 1), base + ADE_CLIP_SIZE0(ch));
writel(clip_left << 16 | clip_right, base + ADE_CLIP_SIZE1(ch));
ade_update_reload_bit(base, CLIP_OFST + ch, 0);
}
static void ade_clip_disable(void __iomem *base, u32 ch)
{
writel(1, base + ADE_CLIP_DISABLE(ch));
ade_update_reload_bit(base, CLIP_OFST + ch, 1);
}
static bool has_Alpha_channel(int format)
{
switch (format) {
case ADE_ARGB_8888:
case ADE_ABGR_8888:
case ADE_RGBA_8888:
case ADE_BGRA_8888:
return true;
default:
return false;
}
}
static void ade_get_blending_params(u32 fmt, u8 glb_alpha, u8 *alp_mode,
u8 *alp_sel, u8 *under_alp_sel)
{
bool has_alpha = has_Alpha_channel(fmt);
/*
* get alp_mode
*/
if (has_alpha && glb_alpha < 255)
*alp_mode = ADE_ALP_PIXEL_AND_GLB;
else if (has_alpha)
*alp_mode = ADE_ALP_PIXEL;
else
*alp_mode = ADE_ALP_GLOBAL;
/*
* get alp sel
*/
*alp_sel = ADE_ALP_MUL_COEFF_3; /* 1 */
*under_alp_sel = ADE_ALP_MUL_COEFF_2; /* 0 */
}
static void ade_compositor_routing_set(void __iomem *base, u8 ch,
u32 x0, u32 y0,
u32 in_w, u32 in_h, u32 fmt)
{
u8 ovly_ch = 0; /* TODO: This is the zpos, only one plane now */
u8 glb_alpha = 255;
u32 x1 = x0 + in_w - 1;
u32 y1 = y0 + in_h - 1;
u32 val;
u8 alp_sel;
u8 under_alp_sel;
u8 alp_mode;
ade_get_blending_params(fmt, glb_alpha, &alp_mode, &alp_sel,
&under_alp_sel);
/* overlay routing setting
*/
writel(x0 << 16 | y0, base + ADE_OVLY_CH_XY0(ovly_ch));
writel(x1 << 16 | y1, base + ADE_OVLY_CH_XY1(ovly_ch));
val = (ch + 1) << CH_SEL_OFST | BIT(CH_EN_OFST) |
alp_sel << CH_ALP_SEL_OFST |
under_alp_sel << CH_UNDER_ALP_SEL_OFST |
glb_alpha << CH_ALP_GBL_OFST |
alp_mode << CH_ALP_MODE_OFST;
writel(val, base + ADE_OVLY_CH_CTL(ovly_ch));
/* connect this plane/channel to overlay2 compositor */
ade_update_bits(base + ADE_OVLY_CTL, CH_OVLY_SEL_OFST(ovly_ch),
CH_OVLY_SEL_MASK, CH_OVLY_SEL_VAL(OUT_OVLY));
}
static void ade_compositor_routing_disable(void __iomem *base, u32 ch)
{
u8 ovly_ch = 0; /* TODO: Only primary plane now */
/* disable this plane/channel */
ade_update_bits(base + ADE_OVLY_CH_CTL(ovly_ch), CH_EN_OFST,
MASK(1), 0);
/* dis-connect this plane/channel of overlay2 compositor */
ade_update_bits(base + ADE_OVLY_CTL, CH_OVLY_SEL_OFST(ovly_ch),
CH_OVLY_SEL_MASK, 0);
}
/*
* Typicaly, a channel looks like: DMA-->clip-->scale-->ctrans-->compositor
*/
static void ade_update_channel(struct kirin_plane *kplane,
struct drm_framebuffer *fb, int crtc_x,
int crtc_y, unsigned int crtc_w,
unsigned int crtc_h, u32 src_x,
u32 src_y, u32 src_w, u32 src_h)
{
struct ade_hw_ctx *ctx = kplane->hw_ctx;
void __iomem *base = ctx->base;
u32 fmt = ade_get_format(fb->format->format);
u32 ch = kplane->ch;
u32 in_w;
u32 in_h;
DRM_DEBUG_DRIVER("channel%d: src:(%d, %d)-%dx%d, crtc:(%d, %d)-%dx%d",
ch + 1, src_x, src_y, src_w, src_h,
crtc_x, crtc_y, crtc_w, crtc_h);
/* 1) DMA setting */
in_w = src_w;
in_h = src_h;
ade_rdma_set(base, fb, ch, src_y, in_h, fmt);
/* 2) clip setting */
ade_clip_set(base, ch, fb->width, src_x, in_w, in_h);
/* 3) TODO: scale setting for overlay planes */
/* 4) TODO: ctran/csc setting for overlay planes */
/* 5) compositor routing setting */
ade_compositor_routing_set(base, ch, crtc_x, crtc_y, in_w, in_h, fmt);
}
static void ade_disable_channel(struct kirin_plane *kplane)
{
struct ade_hw_ctx *ctx = kplane->hw_ctx;
void __iomem *base = ctx->base;
u32 ch = kplane->ch;
DRM_DEBUG_DRIVER("disable channel%d\n", ch + 1);
/* disable read DMA */
ade_rdma_disable(base, ch);
/* disable clip */
ade_clip_disable(base, ch);
/* disable compositor routing */
ade_compositor_routing_disable(base, ch);
}
static int ade_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_x = new_plane_state->src_x >> 16;
u32 src_y = new_plane_state->src_y >> 16;
u32 src_w = new_plane_state->src_w >> 16;
u32 src_h = new_plane_state->src_h >> 16;
int crtc_x = new_plane_state->crtc_x;
int crtc_y = new_plane_state->crtc_y;
u32 crtc_w = new_plane_state->crtc_w;
u32 crtc_h = new_plane_state->crtc_h;
u32 fmt;
if (!crtc || !fb)
return 0;
fmt = ade_get_format(fb->format->format);
if (fmt == ADE_FORMAT_UNSUPPORT)
return -EINVAL;
crtc_state = drm_atomic_get_crtc_state(state, crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
if (src_w != crtc_w || src_h != crtc_h) {
return -EINVAL;
}
if (src_x + src_w > fb->width ||
src_y + src_h > fb->height)
return -EINVAL;
if (crtc_x < 0 || crtc_y < 0)
return -EINVAL;
if (crtc_x + crtc_w > crtc_state->adjusted_mode.hdisplay ||
crtc_y + crtc_h > crtc_state->adjusted_mode.vdisplay)
return -EINVAL;
return 0;
}
static void ade_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);
struct kirin_plane *kplane = to_kirin_plane(plane);
ade_update_channel(kplane, new_state->fb, new_state->crtc_x,
new_state->crtc_y,
new_state->crtc_w, new_state->crtc_h,
new_state->src_x >> 16, new_state->src_y >> 16,
new_state->src_w >> 16, new_state->src_h >> 16);
}
static void ade_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct kirin_plane *kplane = to_kirin_plane(plane);
ade_disable_channel(kplane);
}
static const struct drm_plane_helper_funcs ade_plane_helper_funcs = {
.atomic_check = ade_plane_atomic_check,
.atomic_update = ade_plane_atomic_update,
.atomic_disable = ade_plane_atomic_disable,
};
static struct drm_plane_funcs ade_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 void *ade_hw_ctx_alloc(struct platform_device *pdev,
struct drm_crtc *crtc)
{
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct ade_hw_ctx *ctx = NULL;
int ret;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
DRM_ERROR("failed to alloc ade_hw_ctx\n");
return ERR_PTR(-ENOMEM);
}
ctx->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctx->base)) {
DRM_ERROR("failed to remap ade io base\n");
return ERR_PTR(-EIO);
}
ctx->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(ctx->reset))
return ERR_PTR(-ENODEV);
ctx->noc_regmap =
syscon_regmap_lookup_by_phandle(np, "hisilicon,noc-syscon");
if (IS_ERR(ctx->noc_regmap)) {
DRM_ERROR("failed to get noc regmap\n");
return ERR_PTR(-ENODEV);
}
ctx->irq = platform_get_irq(pdev, 0);
if (ctx->irq < 0) {
DRM_ERROR("failed to get irq\n");
return ERR_PTR(-ENODEV);
}
ctx->ade_core_clk = devm_clk_get(dev, "clk_ade_core");
if (IS_ERR(ctx->ade_core_clk)) {
DRM_ERROR("failed to parse clk ADE_CORE\n");
return ERR_PTR(-ENODEV);
}
ctx->media_noc_clk = devm_clk_get(dev, "clk_codec_jpeg");
if (IS_ERR(ctx->media_noc_clk)) {
DRM_ERROR("failed to parse clk CODEC_JPEG\n");
return ERR_PTR(-ENODEV);
}
ctx->ade_pix_clk = devm_clk_get(dev, "clk_ade_pix");
if (IS_ERR(ctx->ade_pix_clk)) {
DRM_ERROR("failed to parse clk ADE_PIX\n");
return ERR_PTR(-ENODEV);
}
/* vblank irq init */
ret = devm_request_irq(dev, ctx->irq, ade_irq_handler,
IRQF_SHARED, dev->driver->name, ctx);
if (ret)
return ERR_PTR(-EIO);
ctx->crtc = crtc;
return ctx;
}
static void ade_hw_ctx_cleanup(void *hw_ctx)
{
}
static const struct drm_mode_config_funcs ade_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
DEFINE_DRM_GEM_DMA_FOPS(ade_fops);
static const struct drm_driver ade_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &ade_fops,
DRM_GEM_DMA_DRIVER_OPS,
DRM_FBDEV_DMA_DRIVER_OPS,
.name = "kirin",
.desc = "Hisilicon Kirin620 SoC DRM Driver",
.major = 1,
.minor = 0,
};
struct kirin_drm_data ade_driver_data = {
.num_planes = ADE_CH_NUM,
.prim_plane = ADE_CH1,
.channel_formats = channel_formats,
.channel_formats_cnt = ARRAY_SIZE(channel_formats),
.config_max_width = 2048,
.config_max_height = 2048,
.driver = &ade_driver,
.crtc_helper_funcs = &ade_crtc_helper_funcs,
.crtc_funcs = &ade_crtc_funcs,
.plane_helper_funcs = &ade_plane_helper_funcs,
.plane_funcs = &ade_plane_funcs,
.mode_config_funcs = &ade_mode_config_funcs,
.alloc_hw_ctx = ade_hw_ctx_alloc,
.cleanup_hw_ctx = ade_hw_ctx_cleanup,
};
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Hisilicon Kirin SoCs drm master driver
*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*
* Author:
* Xinliang Liu <z.liuxinliang@hisilicon.com>
* Xinliang Liu <xinliang.liu@linaro.org>
* Xinwei Kong <kong.kongxinwei@hisilicon.com>
*/
#include <linux/component.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_module.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include "kirin_drm_drv.h"
#define KIRIN_MAX_PLANE 2
struct kirin_drm_private {
struct kirin_crtc crtc;
struct kirin_plane planes[KIRIN_MAX_PLANE];
void *hw_ctx;
};
static int kirin_drm_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
struct drm_plane *plane,
const struct kirin_drm_data *driver_data)
{
struct device_node *port;
int ret;
/* set crtc port so that
* drm_of_find_possible_crtcs call works
*/
port = of_get_child_by_name(dev->dev->of_node, "port");
if (!port) {
DRM_ERROR("no port node found in %pOF\n", dev->dev->of_node);
return -EINVAL;
}
of_node_put(port);
crtc->port = port;
ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
driver_data->crtc_funcs, NULL);
if (ret) {
DRM_ERROR("failed to init crtc.\n");
return ret;
}
drm_crtc_helper_add(crtc, driver_data->crtc_helper_funcs);
return 0;
}
static int kirin_drm_plane_init(struct drm_device *dev, struct drm_plane *plane,
enum drm_plane_type type,
const struct kirin_drm_data *data)
{
int ret = 0;
ret = drm_universal_plane_init(dev, plane, 1, data->plane_funcs,
data->channel_formats,
data->channel_formats_cnt,
NULL, type, NULL);
if (ret) {
DRM_ERROR("fail to init plane, ch=%d\n", 0);
return ret;
}
drm_plane_helper_add(plane, data->plane_helper_funcs);
return 0;
}
static void kirin_drm_private_cleanup(struct drm_device *dev)
{
struct kirin_drm_private *kirin_priv = dev->dev_private;
struct kirin_drm_data *data;
data = (struct kirin_drm_data *)of_device_get_match_data(dev->dev);
if (data->cleanup_hw_ctx)
data->cleanup_hw_ctx(kirin_priv->hw_ctx);
devm_kfree(dev->dev, kirin_priv);
dev->dev_private = NULL;
}
static int kirin_drm_private_init(struct drm_device *dev,
const struct kirin_drm_data *driver_data)
{
struct platform_device *pdev = to_platform_device(dev->dev);
struct kirin_drm_private *kirin_priv;
struct drm_plane *prim_plane;
enum drm_plane_type type;
void *ctx;
int ret;
u32 ch;
kirin_priv = devm_kzalloc(dev->dev, sizeof(*kirin_priv), GFP_KERNEL);
if (!kirin_priv) {
DRM_ERROR("failed to alloc kirin_drm_private\n");
return -ENOMEM;
}
ctx = driver_data->alloc_hw_ctx(pdev, &kirin_priv->crtc.base);
if (IS_ERR(ctx)) {
DRM_ERROR("failed to initialize kirin_priv hw ctx\n");
return -EINVAL;
}
kirin_priv->hw_ctx = ctx;
/*
* plane init
* TODO: Now only support primary plane, overlay planes
* need to do.
*/
for (ch = 0; ch < driver_data->num_planes; ch++) {
if (ch == driver_data->prim_plane)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
ret = kirin_drm_plane_init(dev, &kirin_priv->planes[ch].base,
type, driver_data);
if (ret)
return ret;
kirin_priv->planes[ch].ch = ch;
kirin_priv->planes[ch].hw_ctx = ctx;
}
/* crtc init */
prim_plane = &kirin_priv->planes[driver_data->prim_plane].base;
ret = kirin_drm_crtc_init(dev, &kirin_priv->crtc.base,
prim_plane, driver_data);
if (ret)
return ret;
kirin_priv->crtc.hw_ctx = ctx;
dev->dev_private = kirin_priv;
return 0;
}
static int kirin_drm_kms_init(struct drm_device *dev,
const struct kirin_drm_data *driver_data)
{
int ret;
/* dev->mode_config initialization */
drm_mode_config_init(dev);
dev->mode_config.min_width = 0;
dev->mode_config.min_height = 0;
dev->mode_config.max_width = driver_data->config_max_width;
dev->mode_config.max_height = driver_data->config_max_width;
dev->mode_config.funcs = driver_data->mode_config_funcs;
/* display controller init */
ret = kirin_drm_private_init(dev, driver_data);
if (ret)
goto err_mode_config_cleanup;
/* bind and init sub drivers */
ret = component_bind_all(dev->dev, dev);
if (ret) {
DRM_ERROR("failed to bind all component.\n");
goto err_private_cleanup;
}
/* vblank init */
ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
if (ret) {
DRM_ERROR("failed to initialize vblank.\n");
goto err_unbind_all;
}
/* reset all the states of crtc/plane/encoder/connector */
drm_mode_config_reset(dev);
/* init kms poll for handling hpd */
drm_kms_helper_poll_init(dev);
return 0;
err_unbind_all:
component_unbind_all(dev->dev, dev);
err_private_cleanup:
kirin_drm_private_cleanup(dev);
err_mode_config_cleanup:
drm_mode_config_cleanup(dev);
return ret;
}
static int kirin_drm_kms_cleanup(struct drm_device *dev)
{
drm_kms_helper_poll_fini(dev);
drm_atomic_helper_shutdown(dev);
kirin_drm_private_cleanup(dev);
drm_mode_config_cleanup(dev);
return 0;
}
static int kirin_drm_bind(struct device *dev)
{
struct kirin_drm_data *driver_data;
struct drm_device *drm_dev;
int ret;
driver_data = (struct kirin_drm_data *)of_device_get_match_data(dev);
if (!driver_data)
return -EINVAL;
drm_dev = drm_dev_alloc(driver_data->driver, dev);
if (IS_ERR(drm_dev))
return PTR_ERR(drm_dev);
dev_set_drvdata(dev, drm_dev);
/* display controller init */
ret = kirin_drm_kms_init(drm_dev, driver_data);
if (ret)
goto err_drm_dev_put;
ret = drm_dev_register(drm_dev, 0);
if (ret)
goto err_kms_cleanup;
drm_client_setup(drm_dev, NULL);
return 0;
err_kms_cleanup:
kirin_drm_kms_cleanup(drm_dev);
err_drm_dev_put:
drm_dev_put(drm_dev);
dev_set_drvdata(dev, NULL);
return ret;
}
static void kirin_drm_unbind(struct device *dev)
{
struct drm_device *drm_dev = dev_get_drvdata(dev);
drm_dev_unregister(drm_dev);
kirin_drm_kms_cleanup(drm_dev);
drm_dev_put(drm_dev);
dev_set_drvdata(dev, NULL);
}
static const struct component_master_ops kirin_drm_ops = {
.bind = kirin_drm_bind,
.unbind = kirin_drm_unbind,
};
static int kirin_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct component_match *match = NULL;
struct device_node *remote;
remote = of_graph_get_remote_node(np, 0, 0);
if (!remote)
return -ENODEV;
drm_of_component_match_add(dev, &match, component_compare_of, remote);
of_node_put(remote);
return component_master_add_with_match(dev, &kirin_drm_ops, match);
}
static void kirin_drm_platform_remove(struct platform_device *pdev)
{
component_master_del(&pdev->dev, &kirin_drm_ops);
}
static void kirin_drm_platform_shutdown(struct platform_device *pdev)
{
drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
}
static const struct of_device_id kirin_drm_dt_ids[] = {
{ .compatible = "hisilicon,hi6220-ade",
.data = &ade_driver_data,
},
{ /* end node */ },
};
MODULE_DEVICE_TABLE(of, kirin_drm_dt_ids);
static struct platform_driver kirin_drm_platform_driver = {
.probe = kirin_drm_platform_probe,
.remove = kirin_drm_platform_remove,
.shutdown = kirin_drm_platform_shutdown,
.driver = {
.name = "kirin-drm",
.of_match_table = kirin_drm_dt_ids,
},
};
drm_module_platform_driver(kirin_drm_platform_driver);
MODULE_AUTHOR("Xinliang Liu <xinliang.liu@linaro.org>");
MODULE_AUTHOR("Xinliang Liu <z.liuxinliang@hisilicon.com>");
MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
MODULE_DESCRIPTION("hisilicon Kirin SoCs' DRM master driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2016 Linaro Limited.
* Copyright (c) 2014-2016 HiSilicon Limited.
*/
#ifndef __KIRIN_DRM_DRV_H__
#define __KIRIN_DRM_DRV_H__
#define to_kirin_crtc(crtc) \
container_of(crtc, struct kirin_crtc, base)
#define to_kirin_plane(plane) \
container_of(plane, struct kirin_plane, base)
/* kirin-format translate table */
struct kirin_format {
u32 pixel_format;
u32 hw_format;
};
struct kirin_crtc {
struct drm_crtc base;
void *hw_ctx;
bool enable;
};
struct kirin_plane {
struct drm_plane base;
void *hw_ctx;
u32 ch;
};
/* display controller init/cleanup ops */
struct kirin_drm_data {
const u32 *channel_formats;
u32 channel_formats_cnt;
int config_max_width;
int config_max_height;
u32 num_planes;
u32 prim_plane;
const struct drm_driver *driver;
const struct drm_crtc_helper_funcs *crtc_helper_funcs;
const struct drm_crtc_funcs *crtc_funcs;
const struct drm_plane_helper_funcs *plane_helper_funcs;
const struct drm_plane_funcs *plane_funcs;
const struct drm_mode_config_funcs *mode_config_funcs;
void *(*alloc_hw_ctx)(struct platform_device *pdev,
struct drm_crtc *crtc);
void (*cleanup_hw_ctx)(void *hw_ctx);
};
extern struct kirin_drm_data ade_driver_data;
#endif /* __KIRIN_DRM_DRV_H__ */