mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-05 18:41:58 +09:00
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:
@@ -39,6 +39,7 @@
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_self_refresh_helper.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
#include <drm/drm_writeback.h>
|
||||
|
||||
#ifdef CONFIG_DRM_ANALOGIX_DP
|
||||
#include <drm/bridge/analogix_dp.h>
|
||||
@@ -105,6 +106,9 @@
|
||||
#define VOP_CTRL_SET(x, name, v) \
|
||||
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) \
|
||||
vop_read_reg(vop, 0, &vop->data->ctrl->name)
|
||||
|
||||
@@ -115,6 +119,12 @@
|
||||
REG_SET_MASK(vop, name, 0, vop->data->intr->name, \
|
||||
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) \
|
||||
vop_reg_set(vop, &vop->data->group->name, 0, ~0, v, #name)
|
||||
|
||||
@@ -133,11 +143,28 @@
|
||||
VOP_INTR_SET_MASK(vop, name, mask, reg); \
|
||||
} while (0)
|
||||
#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) \
|
||||
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) \
|
||||
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_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)
|
||||
|
||||
enum vop_pending {
|
||||
@@ -221,6 +249,54 @@ struct vop_win {
|
||||
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 rockchip_crtc rockchip_crtc;
|
||||
struct device *dev;
|
||||
@@ -306,6 +382,12 @@ struct vop {
|
||||
struct rockchip_mcu_timing mcu_timing;
|
||||
struct rockchip_mcu_timing mcu_bypass_timing;
|
||||
|
||||
/**
|
||||
* @wb_en: write back enabled
|
||||
*/
|
||||
bool wb_en;
|
||||
struct vop_wb wb;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
uint32_t mask, uint32_t shift, uint32_t v,
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uint32_t i, ret = 0;
|
||||
uint32_t regs = vop_read_reg(vop, 0, reg);
|
||||
|
||||
for (i = 0; i < vop->data->intr->nintrs; i++) {
|
||||
if ((type & vop->data->intr->intrs[i]) && (regs & 1 << i))
|
||||
ret |= vop->data->intr->intrs[i];
|
||||
for (i = 0; i < intr->nintrs; i++) {
|
||||
if ((type & intr->intrs[i]) && (regs & 1 << i))
|
||||
ret |= intr->intrs[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -532,6 +623,13 @@ static inline void vop_cfg_done(struct vop *vop)
|
||||
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)
|
||||
{
|
||||
int i;
|
||||
@@ -811,6 +909,23 @@ static inline bool rockchip_afbc(struct drm_plane *plane, u64 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,
|
||||
uint32_t dst, bool is_horizontal,
|
||||
int vsu_mode, int *vskiplines)
|
||||
@@ -1458,6 +1573,379 @@ static void vop_core_clks_disable(struct vop *vop)
|
||||
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)
|
||||
{
|
||||
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);
|
||||
vop->pre_overlay = s->hdr.pre_overlay;
|
||||
vop_wb_commit(crtc);
|
||||
vop_cfg_done(vop);
|
||||
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 drm_crtc *crtc = &vop->rockchip_crtc.crtc;
|
||||
uint32_t active_irqs;
|
||||
uint32_t wb_irqs;
|
||||
unsigned long flags;
|
||||
int ret = IRQ_NONE;
|
||||
|
||||
@@ -4774,6 +5264,7 @@ static irqreturn_t vop_isr(int irq, void *data)
|
||||
/* Clear all active interrupt sources */
|
||||
if (active_irqs)
|
||||
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);
|
||||
|
||||
@@ -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, alpha_hard_calc, vop->pre_overlay);
|
||||
spin_unlock_irqrestore(&vop->irq_lock, flags);
|
||||
vop_wb_handler(vop);
|
||||
drm_crtc_handle_vblank(crtc);
|
||||
vop_handle_vblank(vop);
|
||||
active_irqs &= ~(FS_INTR | FS_FIELD_INTR);
|
||||
@@ -4830,6 +5322,12 @@ static irqreturn_t vop_isr(int irq, void *data)
|
||||
if (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:
|
||||
vop_core_clks_disable(vop);
|
||||
out:
|
||||
@@ -5470,6 +5968,7 @@ static int vop_bind(struct device *dev, struct device *master, void *data)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
vop_wb_connector_init(vop);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
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);
|
||||
vop_destroy_crtc(vop);
|
||||
|
||||
vop_wb_connector_destory(vop);
|
||||
}
|
||||
|
||||
const struct component_ops vop_component_ops = {
|
||||
|
||||
@@ -1404,11 +1404,43 @@ struct vop_grf_ctrl {
|
||||
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 {
|
||||
const struct vop_reg_data *init_table;
|
||||
unsigned int table_size;
|
||||
const struct vop_ctrl *ctrl;
|
||||
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_csc_table *csc_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 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_MASK (0x1fff << 12)
|
||||
|
||||
|
||||
@@ -159,6 +159,13 @@ static const uint64_t format_modifiers_afbc[] = {
|
||||
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 = {
|
||||
.cbcr_vsd_mode = VOP_REG(RK3288_WIN0_CTRL1, 0x1, 31),
|
||||
.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),
|
||||
};
|
||||
|
||||
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 = {
|
||||
.soc_id = 0x1126b,
|
||||
.vop_id = 0,
|
||||
@@ -1990,6 +2045,8 @@ static const struct vop_data rv1126b_vop = {
|
||||
.max_output = {1920, 1080},
|
||||
.ctrl = &rv1126b_ctrl_data,
|
||||
.intr = &rk3366_lit_intr,
|
||||
.wb = &rv1126b_vop_wb_data,
|
||||
.wb_intr = &rv1126b_wb_intr,
|
||||
.grf = &rv1126b_grf_ctrl,
|
||||
.win = rv1126_vop_win_data,
|
||||
.win_size = ARRAY_SIZE(rv1126_vop_win_data),
|
||||
|
||||
@@ -1041,6 +1041,14 @@
|
||||
|
||||
#define RV1126B_CLK_CNT 0x0040
|
||||
#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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user