drm/rockchip: vop: Add writeback support

RV1126B VOP has a writeback with max 1920 x 1080 output.
Writeback work as a connector in drm system.

Change-Id: I550601e480630a841c9abfadb9eda2204c074592
Signed-off-by: Chaoyi Chen <chaoyi.chen@rock-chips.com>
This commit is contained in:
Chaoyi Chen
2024-11-14 11:11:17 +00:00
committed by Tao Huang
parent 8f17704e58
commit f7e3199891
4 changed files with 608 additions and 5 deletions

View File

@@ -39,6 +39,7 @@
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <drm/drm_self_refresh_helper.h> #include <drm/drm_self_refresh_helper.h>
#include <drm/drm_vblank.h> #include <drm/drm_vblank.h>
#include <drm/drm_writeback.h>
#ifdef CONFIG_DRM_ANALOGIX_DP #ifdef CONFIG_DRM_ANALOGIX_DP
#include <drm/bridge/analogix_dp.h> #include <drm/bridge/analogix_dp.h>
@@ -105,6 +106,9 @@
#define VOP_CTRL_SET(x, name, v) \ #define VOP_CTRL_SET(x, name, v) \
REG_SET(x, name, 0, (x)->data->ctrl->name, v, false) REG_SET(x, name, 0, (x)->data->ctrl->name, v, false)
#define VOP_CTRL_SET2(x, ctrl, name, v) \
REG_SET(x, name, 0, ctrl->regs->name, v, false)
#define VOP_INTR_GET(vop, name) \ #define VOP_INTR_GET(vop, name) \
vop_read_reg(vop, 0, &vop->data->ctrl->name) vop_read_reg(vop, 0, &vop->data->ctrl->name)
@@ -115,6 +119,12 @@
REG_SET_MASK(vop, name, 0, vop->data->intr->name, \ REG_SET_MASK(vop, name, 0, vop->data->intr->name, \
mask, v, false) mask, v, false)
#define VOP_INTR_SET2(vop, intr, name, v) \
REG_SET(vop, name, 0, intr->name, v, false)
#define VOP_INTR_SET_MASK2(vop, intr, name, mask, v) \
REG_SET_MASK(vop, name, 0, intr->name, \
mask, v, false)
#define VOP_REG_SET(vop, group, name, v) \ #define VOP_REG_SET(vop, group, name, v) \
vop_reg_set(vop, &vop->data->group->name, 0, ~0, v, #name) vop_reg_set(vop, &vop->data->group->name, 0, ~0, v, #name)
@@ -133,11 +143,28 @@
VOP_INTR_SET_MASK(vop, name, mask, reg); \ VOP_INTR_SET_MASK(vop, name, mask, reg); \
} while (0) } while (0)
#define VOP_INTR_GET_TYPE(vop, name, type) \ #define VOP_INTR_GET_TYPE(vop, name, type) \
vop_get_intr_type(vop, &vop->data->intr->name, type) vop_get_intr_type(vop, vop->data->intr, &vop->data->intr->name, type)
#define VOP_INTR_SET_TYPE2(vop, intr, name, type, v) \
do { \
int i, reg = 0, mask = 0; \
for (i = 0; i < intr->nintrs; i++) { \
if (intr->intrs[i] & type) { \
reg |= (v) << i; \
mask |= 1 << i; \
} \
} \
VOP_INTR_SET_MASK2(vop, intr, name, mask, reg); \
} while (0)
#define VOP_INTR_GET_TYPE2(vop, intr, name, type) \
vop_get_intr_type(vop, intr, &intr->name, type)
#define VOP_CTRL_GET(x, name) \ #define VOP_CTRL_GET(x, name) \
vop_read_reg(x, 0, &vop->data->ctrl->name) vop_read_reg(x, 0, &vop->data->ctrl->name)
#define VOP_CTRL_GET2(x, ctrl, name) \
vop_read_reg(x, 0, &ctrl->regs->name)
#define VOP_WIN_GET(vop, win, name) \ #define VOP_WIN_GET(vop, win, name) \
vop_read_reg(vop, win->offset, &VOP_WIN_NAME(win, name)) vop_read_reg(vop, win->offset, &VOP_WIN_NAME(win, name))
@@ -156,6 +183,7 @@
#define to_vop_win(x) container_of(x, struct vop_win, base) #define to_vop_win(x) container_of(x, struct vop_win, base)
#define to_vop_plane_state(x) container_of(x, struct vop_plane_state, base) #define to_vop_plane_state(x) container_of(x, struct vop_plane_state, base)
#define to_wb_state(x) container_of(x, struct vop_wb_connector_state, base)
#define AFBC_Y2R_COLOR_TRANSFORM (1 << 4) #define AFBC_Y2R_COLOR_TRANSFORM (1 << 4)
enum vop_pending { enum vop_pending {
@@ -221,6 +249,54 @@ struct vop_win {
struct drm_property *name_prop; struct drm_property *name_prop;
}; };
/*
* max two jobs a time, one is running(writing back),
* another one will run in next frame.
*/
#define VOP_WB_JOB_MAX 2
struct vop_wb_job {
bool pending;
/**
* @fs_vsync_cnt: frame start vysnc counter,
* used to get the write back complete event;
*/
uint32_t fs_vsync_cnt;
};
struct vop_wb {
struct drm_writeback_connector conn;
const struct vop_wb_regs *regs;
struct vop_wb_job jobs[VOP_WB_JOB_MAX];
uint8_t job_index;
/**
* @job_lock:
*
* spinlock to protect the job between vop_wb_commit and vop_wb_handler in isr.
*/
spinlock_t job_lock;
};
enum vop_wb_format {
VOP_WB_ARGB8888,
VOP_WB_RGB888,
VOP_WB_RGB565,
VOP_WB_YUV420SP = 4,
VOP_WB_INVALID = -1,
};
struct vop_wb_connector_state {
struct drm_connector_state base;
dma_addr_t yrgb_addr;
dma_addr_t uv_addr;
enum vop_wb_format format;
bool xgt2_en;
uint16_t scale_x_factor;
uint8_t scale_x_en;
uint8_t scale_y_en;
};
struct vop { struct vop {
struct rockchip_crtc rockchip_crtc; struct rockchip_crtc rockchip_crtc;
struct device *dev; struct device *dev;
@@ -306,6 +382,12 @@ struct vop {
struct rockchip_mcu_timing mcu_timing; struct rockchip_mcu_timing mcu_timing;
struct rockchip_mcu_timing mcu_bypass_timing; struct rockchip_mcu_timing mcu_bypass_timing;
/**
* @wb_en: write back enabled
*/
bool wb_en;
struct vop_wb wb;
struct vop_win win[]; struct vop_win win[];
}; };
@@ -392,6 +474,15 @@ static inline uint32_t vop_read_reg(struct vop *vop, uint32_t base,
return (vop_readl(vop, base + reg->offset) >> reg->shift) & reg->mask; return (vop_readl(vop, base + reg->offset) >> reg->shift) & reg->mask;
} }
static inline void vop_write_reg_uncached(struct vop *vop, const struct vop_reg *reg, uint32_t v)
{
uint32_t offset = reg->offset;
uint32_t cached_val = vop->regsbak[offset >> 2];
v = (cached_val & ~(reg->mask << reg->shift)) | ((v & reg->mask) << reg->shift);
writel(v, vop->regs + offset);
}
static inline void vop_mask_write(struct vop *vop, uint32_t offset, static inline void vop_mask_write(struct vop *vop, uint32_t offset,
uint32_t mask, uint32_t shift, uint32_t v, uint32_t mask, uint32_t shift, uint32_t v,
bool write_mask, bool relaxed) bool write_mask, bool relaxed)
@@ -423,15 +514,15 @@ vop_get_win_phy(struct vop_win *win, const struct vop_reg *reg)
return win->phy; return win->phy;
} }
static inline uint32_t vop_get_intr_type(struct vop *vop, static inline uint32_t vop_get_intr_type(struct vop *vop, const struct vop_intr *intr,
const struct vop_reg *reg, int type) const struct vop_reg *reg, int type)
{ {
uint32_t i, ret = 0; uint32_t i, ret = 0;
uint32_t regs = vop_read_reg(vop, 0, reg); uint32_t regs = vop_read_reg(vop, 0, reg);
for (i = 0; i < vop->data->intr->nintrs; i++) { for (i = 0; i < intr->nintrs; i++) {
if ((type & vop->data->intr->intrs[i]) && (regs & 1 << i)) if ((type & intr->intrs[i]) && (regs & 1 << i))
ret |= vop->data->intr->intrs[i]; ret |= intr->intrs[i];
} }
return ret; return ret;
@@ -532,6 +623,13 @@ static inline void vop_cfg_done(struct vop *vop)
VOP_CTRL_SET(vop, cfg_done, 1); VOP_CTRL_SET(vop, cfg_done, 1);
} }
static inline void vop_wb_cfg_done(struct vop *vop)
{
struct vop_wb *wb = &vop->wb;
VOP_CTRL_SET2(vop, wb, cfg_done, 1);
}
static bool vop_is_allwin_disabled(struct vop *vop) static bool vop_is_allwin_disabled(struct vop *vop)
{ {
int i; int i;
@@ -811,6 +909,23 @@ static inline bool rockchip_afbc(struct drm_plane *plane, u64 modifier)
return rockchip_drm_is_afbc(plane, modifier); return rockchip_drm_is_afbc(plane, modifier);
} }
static enum vop_wb_format vop_convert_wb_format(uint32_t format)
{
switch (format) {
case DRM_FORMAT_ARGB8888:
return VOP_WB_ARGB8888;
case DRM_FORMAT_RGB888:
return VOP_WB_RGB888;
case DRM_FORMAT_RGB565:
return VOP_WB_RGB565;
case DRM_FORMAT_NV12:
return VOP_WB_YUV420SP;
default:
DRM_ERROR("unsupported wb format[%p4cc]\n", &format);
return VOP_WB_INVALID;
}
}
static uint16_t scl_vop_cal_scale(enum scale_mode mode, uint32_t src, static uint16_t scl_vop_cal_scale(enum scale_mode mode, uint32_t src,
uint32_t dst, bool is_horizontal, uint32_t dst, bool is_horizontal,
int vsu_mode, int *vskiplines) int vsu_mode, int *vskiplines)
@@ -1458,6 +1573,379 @@ static void vop_core_clks_disable(struct vop *vop)
clk_disable(vop->hclk); clk_disable(vop->hclk);
} }
static void vop_wb_connector_reset(struct drm_connector *connector)
{
struct vop_wb_connector_state *wb_state;
if (connector->state) {
__drm_atomic_helper_connector_destroy_state(connector->state);
kfree(connector->state);
connector->state = NULL;
}
wb_state = kzalloc(sizeof(*wb_state), GFP_KERNEL);
if (wb_state)
__drm_atomic_helper_connector_reset(connector, &wb_state->base);
}
static enum drm_connector_status
vop_wb_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static void vop_wb_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static struct drm_connector_state *
vop_wb_connector_duplicate_state(struct drm_connector *connector)
{
struct vop_wb_connector_state *wb_state;
if (WARN_ON(!connector->state))
return NULL;
wb_state = kzalloc(sizeof(*wb_state), GFP_KERNEL);
if (!wb_state)
return NULL;
__drm_atomic_helper_connector_duplicate_state(connector, &wb_state->base);
return &wb_state->base;
}
static const struct drm_connector_funcs vop_wb_connector_funcs = {
.reset = vop_wb_connector_reset,
.detect = vop_wb_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = vop_wb_connector_destroy,
.atomic_duplicate_state = vop_wb_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int vop_wb_connector_get_modes(struct drm_connector *connector)
{
return 0;
}
static enum drm_mode_status
vop_wb_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct drm_writeback_connector *wb_conn;
struct vop_wb *wb;
struct vop *vop;
int w, h;
wb_conn = container_of(connector, struct drm_writeback_connector, base);
wb = container_of(wb_conn, struct vop_wb, conn);
vop = container_of(wb, struct vop, wb);
w = mode->hdisplay;
h = mode->vdisplay;
if (w > vop->data->wb->max_output.width)
return MODE_BAD_HVALUE;
if (h > vop->data->wb->max_output.height)
return MODE_BAD_VVALUE;
return MODE_OK;
}
#define VOP_WB_BILI_SCL_FAC_CHECK(src, dst, fac) \
(fac * (dst - 1) >> 12 < (src - 1))
static uint16_t vop_wb_scale_down_factor(uint32_t src, uint32_t dst)
{
uint32_t fac = 0;
int i = 0;
/*
* A workaround to avoid zero div.
*/
if (dst < 2)
dst = 2;
fac = scl_cal_scale2(src, dst);
for (i = 0; i < 100; i++) {
if (VOP_WB_BILI_SCL_FAC_CHECK(src, dst, fac))
break;
fac -= 1;
DRM_DEBUG("down fac cali: src:%d, dst:%d, fac:0x%x\n", src, dst, fac);
}
return fac;
}
static inline bool
vop_wb_connector_changed_only(struct drm_crtc_state *cstate, struct drm_connector *conn)
{
struct drm_crtc_state *old_state;
u32 changed_connectors;
old_state = drm_atomic_get_old_crtc_state(cstate->state, cstate->crtc);
changed_connectors = cstate->connector_mask ^ old_state->connector_mask;
return BIT(drm_connector_index(conn)) == changed_connectors;
}
static int vop_wb_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *cstate,
struct drm_connector_state *conn_state)
{
struct vop_wb_connector_state *wb_state = to_wb_state(conn_state);
struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(cstate);
struct drm_crtc *crtc = cstate->crtc;
struct vop *vop = to_vop(crtc);
struct drm_framebuffer *fb;
struct drm_gem_object *obj, *uv_obj;
struct rockchip_gem_object *rk_obj, *rk_uv_obj;
uint32_t src_width;
/*
* No need for a full modested when the only connector changed is the
* writeback connector.
*/
if (cstate->connectors_changed &&
vop_wb_connector_changed_only(cstate, conn_state->connector)) {
cstate->connectors_changed = false;
drm_dbg(crtc->dev,
"VOP force change connectors_changed to false when only wb changed\n");
}
if (!conn_state->writeback_job || !conn_state->writeback_job->fb)
return 0;
fb = conn_state->writeback_job->fb;
DRM_DEV_DEBUG(vop->dev, "%d x % d\n", fb->width, fb->height);
if (!fb->format->is_yuv && is_yuv_output(vcstate->bus_format)) {
DRM_ERROR("YUV2RGB is not supported by writeback\n");
return -EINVAL;
}
if ((fb->width > cstate->mode.hdisplay) || (fb->width * 4 < cstate->mode.hdisplay) ||
((fb->height < cstate->mode.vdisplay) &&
(fb->height != (cstate->mode.vdisplay >> 1)))) {
drm_dbg_kms(crtc->dev, "Invalid framebuffer size %ux%u, Only support up to 1/4 x scale down and 1/2 y scale down\n",
fb->width, fb->height);
return -EINVAL;
}
wb_state->xgt2_en = fb->width * 2 < cstate->mode.hdisplay;
src_width = wb_state->xgt2_en ? cstate->mode.hdisplay / 2 : cstate->mode.hdisplay;
wb_state->scale_x_factor = vop_wb_scale_down_factor(src_width, fb->width);
wb_state->scale_x_en = (fb->width < cstate->mode.hdisplay) ? 1 : 0;
wb_state->scale_y_en = (fb->height < cstate->mode.vdisplay) ? 1 : 0;
wb_state->format = vop_convert_wb_format(fb->format->format);
if (wb_state->format < 0) {
drm_dbg_kms(crtc->dev, "Invalid pixel format %p4cc\n", &fb->format->format);
return -EINVAL;
}
obj = fb->obj[0];
rk_obj = to_rockchip_obj(obj);
wb_state->yrgb_addr = rk_obj->dma_addr + fb->offsets[0];
if (fb->format->is_yuv) {
uv_obj = fb->obj[1];
rk_uv_obj = to_rockchip_obj(uv_obj);
wb_state->uv_addr = rk_uv_obj->dma_addr + fb->offsets[1];
}
return 0;
}
static void vop_wb_encoder_atomic_disable(struct drm_encoder *encoder,
struct drm_atomic_state *state)
{
struct drm_crtc *crtc = encoder->crtc;
if (!crtc->state->active_changed && !crtc->state->mode_changed) {
crtc->state->connectors_changed = false;
drm_dbg(crtc->dev,
"VOP force change connectors_changed to false when disable wb\n");
}
}
static const struct drm_encoder_helper_funcs vop_wb_encoder_helper_funcs = {
.atomic_check = vop_wb_encoder_atomic_check,
.atomic_disable = vop_wb_encoder_atomic_disable,
};
static const struct drm_connector_helper_funcs vop_wb_connector_helper_funcs = {
.get_modes = vop_wb_connector_get_modes,
.mode_valid = vop_wb_connector_mode_valid,
};
static int vop_wb_connector_init(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
struct drm_crtc *crtc = &vop->rockchip_crtc.crtc;
int ret;
if (!vop_data->wb)
return 0;
vop->wb.regs = vop_data->wb->regs;
vop->wb.conn.encoder.possible_crtcs = drm_crtc_mask(crtc);
spin_lock_init(&vop->wb.job_lock);
drm_connector_helper_add(&vop->wb.conn.base, &vop_wb_connector_helper_funcs);
ret = drm_writeback_connector_init(vop->drm_dev, &vop->wb.conn,
&vop_wb_connector_funcs,
&vop_wb_encoder_helper_funcs,
vop_data->wb->formats,
vop_data->wb->nformats,
vop->wb.conn.encoder.possible_crtcs);
if (ret)
DRM_DEV_ERROR(vop->dev, "writeback connector init failed\n");
return ret;
}
static void vop_wb_connector_destory(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
if (!vop_data->wb)
return;
drm_encoder_cleanup(&vop->wb.conn.encoder);
drm_connector_cleanup(&vop->wb.conn.base);
}
static void vop_wb_irqs_enable(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
const struct vop_intr *intr = vop_data->wb_intr;
uint32_t irqs = VOPL_WB_UV_FIFO_FULL_INTR | VOPL_WB_YRGB_FIFO_FULL_INTR;
VOP_INTR_SET_TYPE2(vop, intr, clear, irqs, 1);
VOP_INTR_SET_TYPE2(vop, intr, enable, irqs, 1);
}
static uint32_t vop_read_and_clear_wb_irqs(struct vop *vop)
{
const struct vop_data *vop_data = vop->data;
const struct vop_intr *intr = vop_data->wb_intr;
uint32_t irqs = VOPL_WB_UV_FIFO_FULL_INTR | VOPL_WB_YRGB_FIFO_FULL_INTR;
uint32_t val;
if (!intr)
return 0;
val = VOP_INTR_GET_TYPE2(vop, intr, status, irqs);
if (val)
VOP_INTR_SET_TYPE2(vop, intr, clear, val, 1);
return val;
}
static void vop_wb_commit(struct drm_crtc *crtc)
{
struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
struct vop *vop = to_vop(crtc);
struct vop_wb *wb = &vop->wb;
struct drm_writeback_connector *wb_conn = &wb->conn;
struct drm_connector_state *conn_state = wb_conn->base.state;
struct vop_wb_connector_state *wb_state;
unsigned long flags;
uint32_t fifo_throd;
uint8_t r2y;
if (!conn_state)
return;
wb_state = to_wb_state(conn_state);
if (conn_state->writeback_job && conn_state->writeback_job->fb) {
struct drm_framebuffer *fb = conn_state->writeback_job->fb;
rockchip_drm_dbg(vop->dev, VOP_DEBUG_WB,
"Enable wb %ux%u fmt: %u pitches: %d addr: %pad",
fb->width, fb->height, wb_state->format,
fb->pitches[0], &wb_state->yrgb_addr);
drm_writeback_queue_job(wb_conn, conn_state);
conn_state->writeback_job = NULL;
spin_lock_irqsave(&wb->job_lock, flags);
wb->jobs[wb->job_index].pending = true;
wb->job_index++;
if (wb->job_index >= VOP_WB_JOB_MAX)
wb->job_index = 0;
spin_unlock_irqrestore(&wb->job_lock, flags);
fifo_throd = fb->pitches[0] >> 4;
if (fifo_throd > vop->data->wb->fifo_depth)
fifo_throd = vop->data->wb->fifo_depth;
r2y = !vcstate->yuv_overlay && fb->format->is_yuv;
VOP_CTRL_SET2(vop, wb, format, wb_state->format);
VOP_CTRL_SET2(vop, wb, yrgb_mst, wb_state->yrgb_addr);
VOP_CTRL_SET2(vop, wb, uv_mst, wb_state->uv_addr);
VOP_CTRL_SET2(vop, wb, fifo_throd, fifo_throd);
VOP_CTRL_SET2(vop, wb, xgt2_en, wb_state->xgt2_en);
VOP_CTRL_SET2(vop, wb, scale_x_factor, wb_state->scale_x_factor);
VOP_CTRL_SET2(vop, wb, scale_x_en, wb_state->scale_x_en);
VOP_CTRL_SET2(vop, wb, scale_y_en, wb_state->scale_y_en);
VOP_CTRL_SET2(vop, wb, r2y_en, r2y);
VOP_CTRL_SET2(vop, wb, act_width, fb->width - 1);
VOP_CTRL_SET2(vop, wb, vir_stride, fb->pitches[0] >> 2);
VOP_CTRL_SET2(vop, wb, vir_stride_en, 1);
VOP_CTRL_SET2(vop, wb, post_empty_stop_en, 1);
/* RV1126B support one-shot mode */
VOP_CTRL_SET2(vop, wb, one_frame_mode, 1);
/* In one-shot mode, enable bit will auto clear */
vop_write_reg_uncached(vop, &wb->regs->enable, 1);
vop_wb_irqs_enable(vop);
}
}
static void __maybe_unused vop_wb_disable(struct vop *vop)
{
struct vop_wb *wb = &vop->wb;
VOP_CTRL_SET2(vop, wb, enable, 0);
vop_wb_cfg_done(vop);
}
static void vop_wb_handler(struct vop *vop)
{
struct vop_wb *wb = &vop->wb;
struct vop_wb_job *job;
unsigned long flags;
uint8_t i;
if (!wb->regs)
return;
/* In one shot mode, wb_en is auto disable */
spin_lock_irqsave(&wb->job_lock, flags);
for (i = 0; i < VOP_WB_JOB_MAX; i++) {
job = &wb->jobs[i];
if (job->pending) {
job->fs_vsync_cnt++;
if (job->fs_vsync_cnt == 2) {
job->pending = false;
job->fs_vsync_cnt = 0;
drm_writeback_signal_completion(&vop->wb.conn, 0);
}
}
}
spin_unlock_irqrestore(&wb->job_lock, flags);
}
static void vop_crtc_load_lut(struct drm_crtc *crtc) static void vop_crtc_load_lut(struct drm_crtc *crtc)
{ {
struct vop *vop = to_vop(crtc); struct vop *vop = to_vop(crtc);
@@ -4430,6 +4918,7 @@ static void vop_crtc_atomic_flush(struct drm_crtc *crtc,
spin_lock_irqsave(&vop->irq_lock, flags); spin_lock_irqsave(&vop->irq_lock, flags);
vop->pre_overlay = s->hdr.pre_overlay; vop->pre_overlay = s->hdr.pre_overlay;
vop_wb_commit(crtc);
vop_cfg_done(vop); vop_cfg_done(vop);
rockchip_drm_dbg(vop->dev, VOP_DEBUG_CFG_DONE, "cfg_done\n"); rockchip_drm_dbg(vop->dev, VOP_DEBUG_CFG_DONE, "cfg_done\n");
/* /*
@@ -4749,6 +5238,7 @@ static irqreturn_t vop_isr(int irq, void *data)
struct vop *vop = data; struct vop *vop = data;
struct drm_crtc *crtc = &vop->rockchip_crtc.crtc; struct drm_crtc *crtc = &vop->rockchip_crtc.crtc;
uint32_t active_irqs; uint32_t active_irqs;
uint32_t wb_irqs;
unsigned long flags; unsigned long flags;
int ret = IRQ_NONE; int ret = IRQ_NONE;
@@ -4774,6 +5264,7 @@ static irqreturn_t vop_isr(int irq, void *data)
/* Clear all active interrupt sources */ /* Clear all active interrupt sources */
if (active_irqs) if (active_irqs)
VOP_INTR_SET_TYPE(vop, clear, active_irqs, 1); VOP_INTR_SET_TYPE(vop, clear, active_irqs, 1);
wb_irqs = vop_read_and_clear_wb_irqs(vop);
spin_unlock_irqrestore(&vop->irq_lock, flags); spin_unlock_irqrestore(&vop->irq_lock, flags);
@@ -4803,6 +5294,7 @@ static irqreturn_t vop_isr(int irq, void *data)
VOP_CTRL_SET(vop, level2_overlay_en, vop->pre_overlay); VOP_CTRL_SET(vop, level2_overlay_en, vop->pre_overlay);
VOP_CTRL_SET(vop, alpha_hard_calc, vop->pre_overlay); VOP_CTRL_SET(vop, alpha_hard_calc, vop->pre_overlay);
spin_unlock_irqrestore(&vop->irq_lock, flags); spin_unlock_irqrestore(&vop->irq_lock, flags);
vop_wb_handler(vop);
drm_crtc_handle_vblank(crtc); drm_crtc_handle_vblank(crtc);
vop_handle_vblank(vop); vop_handle_vblank(vop);
active_irqs &= ~(FS_INTR | FS_FIELD_INTR); active_irqs &= ~(FS_INTR | FS_FIELD_INTR);
@@ -4830,6 +5322,12 @@ static irqreturn_t vop_isr(int irq, void *data)
if (active_irqs) if (active_irqs)
DRM_ERROR("Unknown VOP IRQs: %#02x\n", active_irqs); DRM_ERROR("Unknown VOP IRQs: %#02x\n", active_irqs);
if (wb_irqs) {
active_irqs = wb_irqs;
ERROR_HANDLER(VOPL_WB_UV_FIFO_FULL);
ERROR_HANDLER(VOPL_WB_YRGB_FIFO_FULL);
}
out_disable: out_disable:
vop_core_clks_disable(vop); vop_core_clks_disable(vop);
out: out:
@@ -5470,6 +5968,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
if (ret) if (ret)
return ret; return ret;
vop_wb_connector_init(vop);
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
if (of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells") > 1) { if (of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells") > 1) {
@@ -5558,6 +6057,8 @@ static void vop_unbind(struct device *dev, struct device *master, void *data)
pm_runtime_disable(dev); pm_runtime_disable(dev);
vop_destroy_crtc(vop); vop_destroy_crtc(vop);
vop_wb_connector_destory(vop);
} }
const struct component_ops vop_component_ops = { const struct component_ops vop_component_ops = {

View File

@@ -1404,11 +1404,43 @@ struct vop_grf_ctrl {
struct vop_reg grf_mipi_1to4_en; struct vop_reg grf_mipi_1to4_en;
}; };
struct vop_wb_regs {
struct vop_reg cfg_done;
struct vop_reg enable;
struct vop_reg format;
struct vop_reg dither_en;
struct vop_reg r2y_en;
struct vop_reg yrgb_mst;
struct vop_reg uv_mst;
struct vop_reg fifo_throd;
struct vop_reg scale_x_factor;
struct vop_reg scale_x_en;
struct vop_reg scale_y_en;
struct vop_reg axi_yrgb_id;
struct vop_reg axi_uv_id;
struct vop_reg vir_stride;
struct vop_reg vir_stride_en;
struct vop_reg act_width;
struct vop_reg post_empty_stop_en;
struct vop_reg one_frame_mode;
struct vop_reg xgt2_en;
};
struct vop_wb_data {
uint32_t nformats;
const uint32_t *formats;
struct vop_rect max_output;
const struct vop_wb_regs *regs;
uint32_t fifo_depth;
};
struct vop_data { struct vop_data {
const struct vop_reg_data *init_table; const struct vop_reg_data *init_table;
unsigned int table_size; unsigned int table_size;
const struct vop_ctrl *ctrl; const struct vop_ctrl *ctrl;
const struct vop_intr *intr; const struct vop_intr *intr;
const struct vop_intr *wb_intr;
const struct vop_wb_data *wb;
const struct vop_win_data *win; const struct vop_win_data *win;
const struct vop_csc_table *csc_table; const struct vop_csc_table *csc_table;
const struct vop_hdr_table *hdr_table; const struct vop_hdr_table *hdr_table;
@@ -1711,6 +1743,11 @@ struct vop2_data {
#define LINE_FLAG_INTR_CLR (1 << (INTR_CLR_SHIFT + 2)) #define LINE_FLAG_INTR_CLR (1 << (INTR_CLR_SHIFT + 2))
#define BUS_ERROR_INTR_CLR (1 << (INTR_CLR_SHIFT + 3)) #define BUS_ERROR_INTR_CLR (1 << (INTR_CLR_SHIFT + 3))
/* RV1126 VOP Lite WB intr define */
#define VOPL_WB_YRGB_FIFO_FULL_INTR BIT(0)
#define VOPL_WB_UV_FIFO_FULL_INTR BIT(1)
#define VOPL_WB_COMPLETE_INTR BIT(4)
#define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12) #define DSP_LINE_NUM(x) (((x) & 0x1fff) << 12)
#define DSP_LINE_NUM_MASK (0x1fff << 12) #define DSP_LINE_NUM_MASK (0x1fff << 12)

View File

@@ -159,6 +159,13 @@ static const uint64_t format_modifiers_afbc[] = {
DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_INVALID,
}; };
static const u32 formats_wb[] = {
DRM_FORMAT_RGB888,
DRM_FORMAT_ARGB8888,
DRM_FORMAT_RGB565,
DRM_FORMAT_NV12,
};
static const struct vop_scl_extension rk3288_win_full_scl_ext = { static const struct vop_scl_extension rk3288_win_full_scl_ext = {
.cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31), .cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31),
.cbcr_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 30), .cbcr_vsu_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 30),
@@ -1982,6 +1989,54 @@ static const struct vop_grf_ctrl rv1126b_grf_ctrl = {
.grf_dclk_inv = VOP_REG(RV1126B_GRF_VOP_LCDC_CON, 0x1, 0), .grf_dclk_inv = VOP_REG(RV1126B_GRF_VOP_LCDC_CON, 0x1, 0),
}; };
static const struct vop_wb_regs rv1126b_vop_wb_regs = {
.cfg_done = VOP_REG_MASK(0, 0x1, 5),
.enable = VOP_REG(RV1126B_WB_CTRL, 0x1, 0),
.format = VOP_REG(RV1126B_WB_CTRL, 0x7, 1),
.dither_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 4),
.r2y_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 5),
.scale_x_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 7),
.scale_y_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 8),
.post_empty_stop_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 11),
.one_frame_mode = VOP_REG(RV1126B_WB_CTRL, 0x1, 12),
.xgt2_en = VOP_REG(RV1126B_WB_CTRL, 0x1, 18),
.axi_yrgb_id = VOP_REG(RV1126B_WB_CTRL, 0xff, 20),
.axi_uv_id = VOP_REG(RV1126B_WB_CTRL, 0x1f, 24),
.fifo_throd = VOP_REG(RV1126B_WB_XSCAL_FACTOR, 0x3ff, 0),
.scale_x_factor = VOP_REG(RV1126B_WB_XSCAL_FACTOR, 0x3fff, 16),
.yrgb_mst = VOP_REG(RV1126B_WB_YRGB_MST, 0xffffffff, 0),
.uv_mst = VOP_REG(RV1126B_WB_CBR_MST, 0xffffffff, 0),
.vir_stride = VOP_REG(RV1126B_WB_VIR_STRIDE, 0x1fff, 0),
.vir_stride_en = VOP_REG(RV1126B_WB_VIR_STRIDE, 0x1, 15),
.act_width = VOP_REG(RV1126B_WB_VIR_STRIDE, 0x1fff, 16),
};
static const int rv1126_wb_intrs[] = {
VOPL_WB_UV_FIFO_FULL_INTR,
VOPL_WB_YRGB_FIFO_FULL_INTR,
VOPL_WB_COMPLETE_INTR,
};
static const struct vop_intr rv1126b_wb_intr = {
.intrs = rv1126_wb_intrs,
.nintrs = ARRAY_SIZE(rv1126_wb_intrs),
.status = VOP_REG_MASK(RV1126B_WB_INTR_STATUS, 0xffff, 0),
.enable = VOP_REG_MASK(RV1126B_WB_INTR_EN, 0xffff, 0),
.clear = VOP_REG_MASK(RV1126B_WB_INTR_CLEAR, 0xffff, 0),
};
static const struct vop_wb_data rv1126b_vop_wb_data = {
.formats = formats_wb,
.nformats = ARRAY_SIZE(formats_wb),
.max_output = { 1920, 1080 },
.fifo_depth = 1920 * 4 / 16,
.regs = &rv1126b_vop_wb_regs,
};
static const struct vop_data rv1126b_vop = { static const struct vop_data rv1126b_vop = {
.soc_id = 0x1126b, .soc_id = 0x1126b,
.vop_id = 0, .vop_id = 0,
@@ -1990,6 +2045,8 @@ static const struct vop_data rv1126b_vop = {
.max_output = {1920, 1080}, .max_output = {1920, 1080},
.ctrl = &rv1126b_ctrl_data, .ctrl = &rv1126b_ctrl_data,
.intr = &rk3366_lit_intr, .intr = &rk3366_lit_intr,
.wb = &rv1126b_vop_wb_data,
.wb_intr = &rv1126b_wb_intr,
.grf = &rv1126b_grf_ctrl, .grf = &rv1126b_grf_ctrl,
.win = rv1126_vop_win_data, .win = rv1126_vop_win_data,
.win_size = ARRAY_SIZE(rv1126_vop_win_data), .win_size = ARRAY_SIZE(rv1126_vop_win_data),

View File

@@ -1041,6 +1041,14 @@
#define RV1126B_CLK_CNT 0x0040 #define RV1126B_CLK_CNT 0x0040
#define RV1126B_GRF_VOP_LCDC_CON 0x30b9c #define RV1126B_GRF_VOP_LCDC_CON 0x30b9c
#define RV1126B_WB_CTRL 0x0280
#define RV1126B_WB_XSCAL_FACTOR 0x0284
#define RV1126B_WB_YRGB_MST 0x0288
#define RV1126B_WB_CBR_MST 0x028c
#define RV1126B_WB_VIR_STRIDE 0x0290
#define RV1126B_WB_INTR_EN 0x02a0
#define RV1126B_WB_INTR_CLEAR 0x02a4
#define RV1126B_WB_INTR_STATUS 0x02a8
#define RK3506_GRF_SOC_CON2 0x0008 #define RK3506_GRF_SOC_CON2 0x0008