diff mbox series

[RFC,v4,4/6] drm/sprd: add Unisoc's drm display controller driver

Message ID 1582710377-15489-5-git-send-email-kevin3.tang@gmail.com (mailing list archive)
State New, archived
Headers show
Series Add Unisoc's drm kms module | expand

Commit Message

Kevin Tang Feb. 26, 2020, 9:46 a.m. UTC
Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.

Cc: Orson Zhai <orsonzhai@gmail.com>
Cc: Baolin Wang <baolin.wang@linaro.org>
Cc: Chunyan Zhang <zhang.lyra@gmail.com>
Signed-off-by: Kevin Tang <kevin.tang@unisoc.com>
---
 drivers/gpu/drm/sprd/Makefile       |   5 +-
 drivers/gpu/drm/sprd/dpu/Makefile   |   7 +
 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 770 ++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sprd/sprd_dpu.c     | 586 +++++++++++++++++++++++++++
 drivers/gpu/drm/sprd/sprd_dpu.h     | 127 ++++++
 drivers/gpu/drm/sprd/sprd_drm.c     |   1 +
 drivers/gpu/drm/sprd/sprd_drm.h     |   2 +
 7 files changed, 1497 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
 create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
 create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
 create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h

Comments

Rob Herring Feb. 27, 2020, 8:37 p.m. UTC | #1
On Wed, Feb 26, 2020 at 3:46 AM Kevin Tang <kevin3.tang@gmail.com> wrote:
>
> Adds DPU(Display Processor Unit) support for the Unisoc's display subsystem.
> It's support multi planes, scaler, rotation, PQ(Picture Quality) and more.
>
> Cc: Orson Zhai <orsonzhai@gmail.com>
> Cc: Baolin Wang <baolin.wang@linaro.org>
> Cc: Chunyan Zhang <zhang.lyra@gmail.com>
> Signed-off-by: Kevin Tang <kevin.tang@unisoc.com>
> ---
>  drivers/gpu/drm/sprd/Makefile       |   5 +-
>  drivers/gpu/drm/sprd/dpu/Makefile   |   7 +
>  drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 770 ++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/sprd/sprd_dpu.c     | 586 +++++++++++++++++++++++++++
>  drivers/gpu/drm/sprd/sprd_dpu.h     | 127 ++++++
>  drivers/gpu/drm/sprd/sprd_drm.c     |   1 +
>  drivers/gpu/drm/sprd/sprd_drm.h     |   2 +
>  7 files changed, 1497 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
>  create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
>  create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
>  create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h
>
> diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
> index 86d95d9..88ab32a 100644
> --- a/drivers/gpu/drm/sprd/Makefile
> +++ b/drivers/gpu/drm/sprd/Makefile
> @@ -2,4 +2,7 @@
>
>  subdir-ccflags-y += -I$(srctree)/$(src)
>
> -obj-y := sprd_drm.o
> +obj-y := sprd_drm.o \
> +       sprd_dpu.o
> +
> +obj-y += dpu/
> diff --git a/drivers/gpu/drm/sprd/dpu/Makefile b/drivers/gpu/drm/sprd/dpu/Makefile
> new file mode 100644
> index 0000000..73bd497
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ifdef CONFIG_ARM64
> +KBUILD_CFLAGS += -mstrict-align
> +endif
> +
> +obj-y += dpu_r2p0.o
> diff --git a/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> new file mode 100644
> index 0000000..984fa9b
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> @@ -0,0 +1,770 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020 Unisoc Inc.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/wait.h>
> +#include <linux/workqueue.h>
> +#include "sprd_dpu.h"
> +
> +#define DISPC_INT_FBC_PLD_ERR_MASK     BIT(8)
> +#define DISPC_INT_FBC_HDR_ERR_MASK     BIT(9)
> +
> +#define DISPC_INT_MMU_INV_WR_MASK      BIT(19)
> +#define DISPC_INT_MMU_INV_RD_MASK      BIT(18)
> +#define DISPC_INT_MMU_VAOR_WR_MASK     BIT(17)
> +#define DISPC_INT_MMU_VAOR_RD_MASK     BIT(16)
> +
> +struct layer_reg {
> +       u32 addr[4];
> +       u32 ctrl;
> +       u32 size;
> +       u32 pitch;
> +       u32 pos;
> +       u32 alpha;
> +       u32 ck;
> +       u32 pallete;
> +       u32 crop_start;
> +};
> +
> +struct wb_region_reg {
> +       u32 pos;
> +       u32 size;
> +};
> +
> +/* dpu controller register */
> +struct dpu_reg {

structs for registers is not normal coding style. #defines of offsets
is. More below.

> +       u32 dpu_version;
> +       u32 dpu_ctrl;
> +       u32 dpu_cfg0;
> +       u32 dpu_cfg1;
> +       u32 dpu_cfg2;
> +       u32 dpu_secure;
> +       u32 reserved_0x0018_0x001C[2];
> +       u32 panel_size;
> +       u32 blend_size;
> +       u32 reserved_0x0028;
> +       u32 bg_color;
> +       struct layer_reg layers[8];
> +       u32 wb_base_addr;
> +       u32 wb_ctrl;
> +       u32 wb_cfg;
> +       u32 wb_pitch;
> +       struct wb_region_reg region[3];
> +       u32 reserved_0x01D8_0x01DC[2];
> +       u32 dpu_int_en;
> +       u32 dpu_int_clr;
> +       u32 dpu_int_sts;
> +       u32 dpu_int_raw;
> +       u32 dpi_ctrl;
> +       u32 dpi_h_timing;
> +       u32 dpi_v_timing;
> +       u32 reserved_0x01FC;
> +       u32 dpu_enhance_cfg;
> +       u32 reserved_0x0204_0x020C[3];
> +       u32 epf_epsilon;
> +       u32 epf_gain0_3;
> +       u32 epf_gain4_7;
> +       u32 epf_diff;
> +       u32 reserved_0x0220_0x023C[8];
> +       u32 hsv_lut_addr;
> +       u32 hsv_lut_wdata;
> +       u32 hsv_lut_rdata;
> +       u32 reserved_0x024C_0x027C[13];
> +       u32 cm_coef01_00;
> +       u32 cm_coef03_02;
> +       u32 cm_coef11_10;
> +       u32 cm_coef13_12;
> +       u32 cm_coef21_20;
> +       u32 cm_coef23_22;
> +       u32 reserved_0x0298_0x02BC[10];
> +       u32 slp_cfg0;
> +       u32 slp_cfg1;
> +       u32 reserved_0x02C8_0x02FC[14];
> +       u32 gamma_lut_addr;
> +       u32 gamma_lut_wdata;
> +       u32 gamma_lut_rdata;
> +       u32 reserved_0x030C_0x033C[13];
> +       u32 checksum_en;
> +       u32 checksum0_start_pos;
> +       u32 checksum0_end_pos;
> +       u32 checksum1_start_pos;
> +       u32 checksum1_end_pos;
> +       u32 checksum0_result;
> +       u32 checksum1_result;
> +       u32 reserved_0x035C;
> +       u32 dpu_sts[18];
> +       u32 reserved_0x03A8_0x03AC[2];
> +       u32 dpu_fbc_cfg0;
> +       u32 dpu_fbc_cfg1;
> +       u32 reserved_0x03B8_0x03EC[14];
> +       u32 rf_ram_addr;
> +       u32 rf_ram_rdata_low;
> +       u32 rf_ram_rdata_high;
> +       u32 reserved_0x03FC_0x07FC[257];
> +       u32 mmu_en;
> +       u32 mmu_update;
> +       u32 mmu_min_vpn;
> +       u32 mmu_vpn_range;
> +       u32 mmu_pt_addr;
> +       u32 mmu_default_page;
> +       u32 mmu_vaor_addr_rd;
> +       u32 mmu_vaor_addr_wr;
> +       u32 mmu_inv_addr_rd;
> +       u32 mmu_inv_addr_wr;
> +       u32 mmu_uns_addr_rd;
> +       u32 mmu_uns_addr_wr;
> +       u32 mmu_miss_cnt;
> +       u32 mmu_pt_update_qos;
> +       u32 mmu_version;
> +       u32 mmu_min_ppn1;
> +       u32 mmu_ppn_range1;
> +       u32 mmu_min_ppn2;
> +       u32 mmu_ppn_range2;
> +       u32 mmu_vpn_paor_rd;
> +       u32 mmu_vpn_paor_wr;
> +       u32 mmu_ppn_paor_rd;
> +       u32 mmu_ppn_paor_wr;
> +       u32 mmu_reg_au_manage;
> +       u32 mmu_page_rd_ch;
> +       u32 mmu_page_wr_ch;
> +       u32 mmu_read_page_cmd_cnt;
> +       u32 mmu_read_page_latency_cnt;
> +       u32 mmu_page_max_latency;
> +};
> +
> +static void dpu_dump(struct dpu_context *ctx)
> +{
> +       u32 *reg = (u32 *)ctx->base;
> +       int i;
> +
> +       pr_info("      0          4          8          C\n");
> +       for (i = 0; i < 256; i += 4) {
> +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);
> +       }
> +}
> +
> +static u32 check_mmu_isr(struct dpu_context *ctx, u32 reg_val)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       u32 mmu_mask = DISPC_INT_MMU_VAOR_RD_MASK |
> +                       DISPC_INT_MMU_VAOR_WR_MASK |
> +                       DISPC_INT_MMU_INV_RD_MASK |
> +                       DISPC_INT_MMU_INV_WR_MASK;
> +       u32 val = reg_val & mmu_mask;
> +
> +       if (val) {
> +               pr_err("--- iommu interrupt err: 0x%04x ---\n", val);
> +
> +               pr_err("iommu invalid read error, addr: 0x%08x\n",
> +                       reg->mmu_inv_addr_rd);
> +               pr_err("iommu invalid write error, addr: 0x%08x\n",
> +                       reg->mmu_inv_addr_wr);
> +               pr_err("iommu va out of range read error, addr: 0x%08x\n",
> +                       reg->mmu_vaor_addr_rd);
> +               pr_err("iommu va out of range write error, addr: 0x%08x\n",
> +                       reg->mmu_vaor_addr_wr);
> +               pr_err("BUG: iommu failure at %s:%d/%s()!\n",
> +                       __FILE__, __LINE__, __func__);
> +
> +               dpu_dump(ctx);
> +       }
> +
> +       return val;
> +}
> +
> +static void dpu_clean_all(struct dpu_context *ctx)
> +{
> +       int i;
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       for (i = 0; i < 8; i++)
> +               reg->layers[i].ctrl = 0;
> +}
> +
> +static u32 dpu_isr(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       u32 reg_val, int_mask = 0;
> +
> +       reg_val = reg->dpu_int_sts;
> +
> +       /* disable err interrupt */
> +       if (reg_val & DISPC_INT_ERR_MASK)
> +               int_mask |= DISPC_INT_ERR_MASK;
> +
> +       /* dpu update done isr */
> +       if (reg_val & DISPC_INT_UPDATE_DONE_MASK) {
> +               ctx->evt_update = true;
> +               wake_up_interruptible_all(&ctx->wait_queue);
> +       }
> +
> +       /* dpu stop done isr */
> +       if (reg_val & DISPC_INT_DONE_MASK) {
> +               ctx->evt_stop = true;
> +               wake_up_interruptible_all(&ctx->wait_queue);
> +       }
> +
> +       /* dpu ifbc payload error isr */
> +       if (reg_val & DISPC_INT_FBC_PLD_ERR_MASK) {
> +               int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> +               pr_err("dpu ifbc payload error\n");
> +       }
> +
> +       /* dpu ifbc header error isr */
> +       if (reg_val & DISPC_INT_FBC_HDR_ERR_MASK) {
> +               int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> +               pr_err("dpu ifbc header error\n");
> +       }
> +
> +       int_mask |= check_mmu_isr(ctx, reg_val);
> +
> +       reg->dpu_int_clr = reg_val;
> +       reg->dpu_int_en &= ~int_mask;

Also, not coding style. Use readl/writel{_relaxed}. This is fragile
because the compiler has a lot of freedom in what it can do here
including reordering the accesses, changing the access size or
skipping the register access altogether. I don't think the latter
would happen in this case, but if a read has side effects such as
causing bits to clear you'll see problems.

> +
> +       return reg_val;
> +}
> +
> +static int dpu_wait_stop_done(struct dpu_context *ctx)
> +{
> +       int rc;
> +
> +       if (ctx->stopped)
> +               return 0;
> +
> +       rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_stop,
> +                                              msecs_to_jiffies(500));
> +       ctx->evt_stop = false;
> +
> +       ctx->stopped = true;
> +
> +       if (!rc) {
> +               pr_err("dpu wait for stop done time out!\n");
> +               return -ETIMEDOUT;
> +       }
> +
> +       return 0;
> +}
> +
> +static int dpu_wait_update_done(struct dpu_context *ctx)
> +{
> +       int rc;
> +
> +       ctx->evt_update = false;
> +
> +       rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_update,
> +                                              msecs_to_jiffies(500));
> +
> +       if (!rc) {
> +               pr_err("dpu wait for reg update done time out!\n");
> +               return -ETIMEDOUT;
> +       }
> +
> +       return 0;
> +}
> +
> +static void dpu_stop(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       if (ctx->stopped)
> +               return;
> +
> +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> +               reg->dpu_ctrl |= BIT(1);
> +
> +       dpu_wait_stop_done(ctx);
> +
> +       pr_info("dpu stop\n");
> +}
> +
> +static void dpu_run(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       if (!ctx->stopped)
> +               return;
> +
> +       reg->dpu_ctrl |= BIT(0);
> +
> +       ctx->stopped = false;
> +
> +       pr_info("dpu run\n");
> +}
> +
> +static int dpu_init(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       u32 size;
> +
> +       reg->bg_color = 0;
> +
> +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> +       reg->panel_size = size;
> +       reg->blend_size = size;
> +
> +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> +
> +       reg->dpu_cfg1 = 0x004466da;
> +       reg->dpu_cfg2 = 0;
> +
> +       if (ctx->stopped)
> +               dpu_clean_all(ctx);
> +
> +       reg->mmu_en = 0;
> +       reg->mmu_min_ppn1 = 0;
> +       reg->mmu_ppn_range1 = 0xffff;
> +       reg->mmu_min_ppn2 = 0;
> +       reg->mmu_ppn_range2 = 0xffff;
> +       reg->mmu_vpn_range = 0x1ffff;
> +
> +       reg->dpu_int_clr = 0xffff;
> +
> +       init_waitqueue_head(&ctx->wait_queue);
> +
> +       return 0;
> +}
> +
> +static void dpu_fini(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       reg->dpu_int_en = 0;
> +       reg->dpu_int_clr = 0xff;
> +}
> +
> +enum {
> +       DPU_LAYER_FORMAT_YUV422_2PLANE,
> +       DPU_LAYER_FORMAT_YUV420_2PLANE,
> +       DPU_LAYER_FORMAT_YUV420_3PLANE,
> +       DPU_LAYER_FORMAT_ARGB8888,
> +       DPU_LAYER_FORMAT_RGB565,
> +       DPU_LAYER_FORMAT_XFBC_ARGB8888 = 8,
> +       DPU_LAYER_FORMAT_XFBC_RGB565,
> +       DPU_LAYER_FORMAT_MAX_TYPES,
> +};
> +
> +enum {
> +       DPU_LAYER_ROTATION_0,
> +       DPU_LAYER_ROTATION_90,
> +       DPU_LAYER_ROTATION_180,
> +       DPU_LAYER_ROTATION_270,
> +       DPU_LAYER_ROTATION_0_M,
> +       DPU_LAYER_ROTATION_90_M,
> +       DPU_LAYER_ROTATION_180_M,
> +       DPU_LAYER_ROTATION_270_M,
> +};
> +
> +static u32 to_dpu_rotation(u32 angle)
> +{
> +       u32 rot = DPU_LAYER_ROTATION_0;
> +
> +       switch (angle) {
> +       case 0:
> +       case DRM_MODE_ROTATE_0:
> +               rot = DPU_LAYER_ROTATION_0;
> +               break;
> +       case DRM_MODE_ROTATE_90:
> +               rot = DPU_LAYER_ROTATION_90;
> +               break;
> +       case DRM_MODE_ROTATE_180:
> +               rot = DPU_LAYER_ROTATION_180;
> +               break;
> +       case DRM_MODE_ROTATE_270:
> +               rot = DPU_LAYER_ROTATION_270;
> +               break;
> +       case DRM_MODE_REFLECT_Y:
> +               rot = DPU_LAYER_ROTATION_180_M;
> +               break;
> +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> +               rot = DPU_LAYER_ROTATION_90_M;
> +               break;
> +       case DRM_MODE_REFLECT_X:
> +               rot = DPU_LAYER_ROTATION_0_M;
> +               break;
> +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> +               rot = DPU_LAYER_ROTATION_270_M;
> +               break;
> +       default:
> +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
> +               break;
> +       }
> +
> +       return rot;
> +}
> +
> +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> +{
> +       int reg_val = 0;
> +
> +       /* layer enable */
> +       reg_val |= BIT(0);
> +
> +       switch (format) {
> +       case DRM_FORMAT_BGRA8888:
> +               /* BGRA8888 -> ARGB8888 */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_RGBX8888:
> +       case DRM_FORMAT_RGBA8888:
> +               /* RGBA8888 -> ABGR8888 */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               /* FALLTHRU */
> +       case DRM_FORMAT_ABGR8888:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_ARGB8888:
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_XBGR8888:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_XRGB8888:
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_BGR565:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_RGB565:
> +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> +               break;
> +       case DRM_FORMAT_NV12:
> +               /* 2-Lane: Yuv420 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_NV21:
> +               /* 2-Lane: Yuv420 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       case DRM_FORMAT_NV16:
> +               /* 2-Lane: Yuv422 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       case DRM_FORMAT_NV61:
> +               /* 2-Lane: Yuv422 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_YUV420:
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_YVU420:
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       default:
> +               pr_err("error: invalid format %c%c%c%c\n", format,
> +                                               format >> 8,
> +                                               format >> 16,
> +                                               format >> 24);
> +               break;
> +       }
> +
> +       switch (blending) {
> +       case DRM_MODE_BLEND_PIXEL_NONE:
> +               /* don't do blending, maybe RGBX */
> +               /* alpha mode select - layer alpha */
> +               reg_val |= BIT(2);
> +               break;
> +       case DRM_MODE_BLEND_COVERAGE:
> +               /* alpha mode select - combo alpha */
> +               reg_val |= BIT(3);
> +               /*Normal mode*/
> +               reg_val &= (~BIT(16));
> +               break;
> +       case DRM_MODE_BLEND_PREMULTI:
> +               /* alpha mode select - combo alpha */
> +               reg_val |= BIT(3);
> +               /*Pre-mult mode*/
> +               reg_val |= BIT(16);
> +               break;
> +       default:
> +               /* alpha mode select - layer alpha */
> +               reg_val |= BIT(2);
> +               break;
> +       }
> +
> +       rotation = to_dpu_rotation(rotation);
> +       reg_val |= (rotation & 0x7) << 20;
> +
> +       return reg_val;
> +}
> +
> +static void dpu_bgcolor(struct dpu_context *ctx, u32 color)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> +               dpu_wait_stop_done(ctx);
> +
> +       reg->bg_color = color;
> +
> +       dpu_clean_all(ctx);
> +
> +       if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->stopped) {
> +               reg->dpu_ctrl |= BIT(2);
> +               dpu_wait_update_done(ctx);
> +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> +               reg->dpu_ctrl |= BIT(0);
> +               ctx->stopped = false;
> +       }
> +}
> +
> +static void dpu_layer(struct dpu_context *ctx,
> +                   struct sprd_dpu_layer *hwlayer)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       const struct drm_format_info *info;
> +       struct layer_reg *layer;
> +       u32 addr, size, offset;
> +       int i;
> +
> +       layer = &reg->layers[hwlayer->index];
> +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> +
> +       if (hwlayer->src_w && hwlayer->src_h)
> +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
> +       else
> +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
> +
> +       for (i = 0; i < hwlayer->planes; i++) {
> +               addr = hwlayer->addr[i];
> +
> +               if (addr % 16)
> +                       pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
> +                               i, addr);
> +               layer->addr[i] = addr;
> +       }
> +
> +       layer->pos = offset;
> +       layer->size = size;
> +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> +       layer->alpha = hwlayer->alpha;
> +
> +       info = drm_format_info(hwlayer->format);
> +       if (info->cpp[0] == 0) {
> +               pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
> +               return;
> +       }
> +
> +       if (hwlayer->planes == 3)
> +               /* UV pitch is 1/2 of Y pitch*/
> +               layer->pitch = (hwlayer->pitch[0] / info->cpp[0]) |
> +                               (hwlayer->pitch[0] / info->cpp[0] << 15);
> +       else
> +               layer->pitch = hwlayer->pitch[0] / info->cpp[0];
> +
> +       layer->ctrl = dpu_img_ctrl(hwlayer->format, hwlayer->blending,
> +               hwlayer->rotation);
> +
> +       pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
> +                               hwlayer->dst_x, hwlayer->dst_y,
> +                               hwlayer->dst_w, hwlayer->dst_h);
> +       pr_debug("start_x = %d, start_y = %d, start_w = %d, start_h = %d\n",
> +                               hwlayer->src_x, hwlayer->src_y,
> +                               hwlayer->src_w, hwlayer->src_h);
> +}
> +
> +static void dpu_flip(struct dpu_context *ctx,
> +                    struct sprd_dpu_layer layers[], u8 count)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       int i;
> +
> +       /*
> +        * Make sure the dpu is in stop status. DPU_R2P0 has no shadow
> +        * registers in EDPI mode. So the config registers can only be
> +        * updated in the rising edge of DPU_RUN bit.
> +        */
> +       if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> +               dpu_wait_stop_done(ctx);
> +
> +       /* reset the bgcolor to black */
> +       reg->bg_color = 0;
> +
> +       /* disable all the layers */
> +       dpu_clean_all(ctx);
> +
> +       /* start configure dpu layers */
> +       for (i = 0; i < count; i++)
> +               dpu_layer(ctx, &layers[i]);
> +
> +       /* update trigger and wait */
> +       if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> +               if (!ctx->stopped) {
> +                       reg->dpu_ctrl |= BIT(2);
> +                       dpu_wait_update_done(ctx);
> +               }
> +
> +               reg->dpu_int_en |= DISPC_INT_ERR_MASK;
> +
> +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> +               reg->dpu_ctrl |= BIT(0);
> +
> +               ctx->stopped = false;
> +       }
> +
> +       /*
> +        * If the following interrupt was disabled in isr,
> +        * re-enable it.
> +        */
> +       reg->dpu_int_en |= DISPC_INT_FBC_PLD_ERR_MASK |
> +                          DISPC_INT_FBC_HDR_ERR_MASK |
> +                          DISPC_INT_MMU_VAOR_RD_MASK |
> +                          DISPC_INT_MMU_VAOR_WR_MASK |
> +                          DISPC_INT_MMU_INV_RD_MASK |
> +                          DISPC_INT_MMU_INV_WR_MASK;
> +}
> +
> +static void dpu_dpi_init(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       u32 int_mask = 0;
> +
> +       if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> +               /* use dpi as interface */
> +               reg->dpu_cfg0 &= ~BIT(0);
> +
> +               /* disable Halt function for SPRD DSI */
> +               reg->dpi_ctrl &= ~BIT(16);
> +
> +               /* select te from external pad */
> +               reg->dpi_ctrl |= BIT(10);
> +
> +               /* set dpi timing */
> +               reg->dpi_h_timing = (ctx->vm.hsync_len << 0) |
> +                                   (ctx->vm.hback_porch << 8) |
> +                                   (ctx->vm.hfront_porch << 20);
> +               reg->dpi_v_timing = (ctx->vm.vsync_len << 0) |
> +                                   (ctx->vm.vback_porch << 8) |
> +                                   (ctx->vm.vfront_porch << 20);
> +               if (ctx->vm.vsync_len + ctx->vm.vback_porch < 32)
> +                       pr_warn("Warning: (vsync + vbp) < 32, "
> +                               "underflow risk!\n");
> +
> +               /* enable dpu update done INT */
> +               int_mask |= DISPC_INT_UPDATE_DONE_MASK;
> +               /* enable dpu DONE  INT */
> +               int_mask |= DISPC_INT_DONE_MASK;
> +               /* enable dpu dpi vsync */
> +               int_mask |= DISPC_INT_DPI_VSYNC_MASK;
> +               /* enable dpu TE INT */
> +               int_mask |= DISPC_INT_TE_MASK;
> +               /* enable underflow err INT */
> +               int_mask |= DISPC_INT_ERR_MASK;
> +               /* enable write back done INT */
> +               int_mask |= DISPC_INT_WB_DONE_MASK;
> +               /* enable write back fail INT */
> +               int_mask |= DISPC_INT_WB_FAIL_MASK;
> +
> +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> +               /* use edpi as interface */
> +               reg->dpu_cfg0 |= BIT(0);
> +
> +               /* use external te */
> +               reg->dpi_ctrl |= BIT(10);
> +
> +               /* enable te */
> +               reg->dpi_ctrl |= BIT(8);
> +
> +               /* enable stop DONE INT */
> +               int_mask |= DISPC_INT_DONE_MASK;
> +               /* enable TE INT */
> +               int_mask |= DISPC_INT_TE_MASK;
> +       }
> +
> +       /* enable ifbc payload error INT */
> +       int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> +       /* enable ifbc header error INT */
> +       int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> +       /* enable iommu va out of range read error INT */
> +       int_mask |= DISPC_INT_MMU_VAOR_RD_MASK;
> +       /* enable iommu va out of range write error INT */
> +       int_mask |= DISPC_INT_MMU_VAOR_WR_MASK;
> +       /* enable iommu invalid read error INT */
> +       int_mask |= DISPC_INT_MMU_INV_RD_MASK;
> +       /* enable iommu invalid write error INT */
> +       int_mask |= DISPC_INT_MMU_INV_WR_MASK;
> +
> +       reg->dpu_int_en = int_mask;
> +}
> +
> +static void enable_vsync(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       reg->dpu_int_en |= DISPC_INT_DPI_VSYNC_MASK;
> +}
> +
> +static void disable_vsync(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       reg->dpu_int_en &= ~DISPC_INT_DPI_VSYNC_MASK;
> +}
> +
> +static const u32 primary_fmts[] = {
> +       DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
> +       DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
> +       DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
> +       DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
> +       DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
> +       DRM_FORMAT_NV12, DRM_FORMAT_NV21,
> +       DRM_FORMAT_NV16, DRM_FORMAT_NV61,
> +       DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
> +};
> +
> +static int dpu_capability(struct dpu_context *ctx,
> +                       struct dpu_capability *cap)
> +{
> +       if (!cap)
> +               return -EINVAL;
> +
> +       cap->max_layers = 6;
> +       cap->fmts_ptr = primary_fmts;
> +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> +
> +       return 0;
> +}
> +
> +const struct dpu_core_ops sharkl3_dpu_core_ops = {
> +       .init = dpu_init,
> +       .fini = dpu_fini,
> +       .run = dpu_run,
> +       .stop = dpu_stop,
> +       .isr = dpu_isr,
> +       .ifconfig = dpu_dpi_init,
> +       .capability = dpu_capability,
> +       .flip = dpu_flip,
> +       .bg_color = dpu_bgcolor,
> +       .enable_vsync = enable_vsync,
> +       .disable_vsync = disable_vsync,
> +};
> diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c
> new file mode 100644
> index 0000000..f122b0e
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_dpu.c
> @@ -0,0 +1,586 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2020 Unisoc Inc.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/dma-buf.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/of_irq.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_plane_helper.h>
> +
> +#include "sprd_drm.h"
> +#include "sprd_dpu.h"
> +
> +struct sprd_plane {
> +       struct drm_plane plane;
> +       u32 index;
> +};
> +
> +static int sprd_dpu_init(struct sprd_dpu *dpu);
> +static int sprd_dpu_fini(struct sprd_dpu *dpu);
> +
> +static inline struct sprd_plane *to_sprd_plane(struct drm_plane *plane)
> +{
> +       return container_of(plane, struct sprd_plane, plane);
> +}
> +
> +static int sprd_plane_atomic_check(struct drm_plane *plane,
> +                                 struct drm_plane_state *state)
> +{
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       return 0;
> +}
> +
> +static void sprd_plane_atomic_update(struct drm_plane *plane,
> +                                   struct drm_plane_state *old_state)
> +{
> +       struct drm_plane_state *state = plane->state;
> +       struct drm_framebuffer *fb = plane->state->fb;
> +       struct drm_gem_cma_object *cma_obj;
> +       struct sprd_plane *p = to_sprd_plane(plane);
> +       struct sprd_dpu *dpu = crtc_to_dpu(plane->state->crtc);
> +       struct sprd_dpu_layer *layer = &dpu->layers[p->index];
> +       int i;
> +
> +       if (plane->state->crtc->state->active_changed) {
> +               DRM_DEBUG("resume or suspend, no need to update plane\n");
> +               return;
> +       }
> +
> +       layer->index = p->index;
> +       layer->src_x = state->src_x >> 16;
> +       layer->src_y = state->src_y >> 16;
> +       layer->src_w = state->src_w >> 16;
> +       layer->src_h = state->src_h >> 16;
> +       layer->dst_x = state->crtc_x;
> +       layer->dst_y = state->crtc_y;
> +       layer->dst_w = state->crtc_w;
> +       layer->dst_h = state->crtc_h;
> +       layer->alpha = state->alpha;
> +       layer->blending = state->pixel_blend_mode;
> +       layer->rotation = state->rotation;
> +       layer->planes = fb->format->num_planes;
> +       layer->format = fb->format->format;
> +
> +       DRM_DEBUG("%s() alpha = %u, blending = %u, rotation = %u\n",
> +                 __func__, layer->alpha, layer->blending, layer->rotation);
> +
> +       for (i = 0; i < layer->planes; i++) {
> +               cma_obj = drm_fb_cma_get_gem_obj(fb, i);
> +               layer->addr[i] = cma_obj->paddr + fb->offsets[i];
> +               layer->pitch[i] = fb->pitches[i];
> +       }
> +
> +       dpu->pending_planes++;
> +}
> +
> +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> +                                    struct drm_plane_state *old_state)
> +{
> +       struct sprd_plane *p = to_sprd_plane(plane);
> +
> +       /*
> +        * NOTE:
> +        * The dpu->core->flip() will disable all the planes each time.
> +        * So there is no need to impliment the atomic_disable() function.
> +        * But this function can not be removed, because it will change
> +        * to call atomic_update() callback instead. Which will cause
> +        * kernel panic in sprd_plane_atomic_update().
> +        *
> +        * We do nothing here but just print a debug log.
> +        */
> +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
> +}
> +
> +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> +{
> +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> +
> +       /* create rotation property */
> +       drm_plane_create_rotation_property(&p->plane,
> +                                          DRM_MODE_ROTATE_0,
> +                                          DRM_MODE_ROTATE_MASK |
> +                                          DRM_MODE_REFLECT_MASK);
> +
> +       /* create alpha property */
> +       drm_plane_create_alpha_property(&p->plane);
> +
> +       /* create blend mode property */
> +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> +
> +       /* create zpos property */
> +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> +
> +       return 0;
> +}
> +
> +static const struct drm_plane_helper_funcs sprd_plane_helper_funcs = {
> +       .atomic_check = sprd_plane_atomic_check,
> +       .atomic_update = sprd_plane_atomic_update,
> +       .atomic_disable = sprd_plane_atomic_disable,
> +};
> +
> +static const struct drm_plane_funcs sprd_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 struct drm_plane *sprd_plane_init(struct drm_device *drm,
> +                                       struct sprd_dpu *dpu)
> +{
> +       struct drm_plane *primary = NULL;
> +       struct sprd_plane *p = NULL;
> +       struct dpu_capability cap = {};
> +       int err, i;
> +
> +       if (dpu->core && dpu->core->capability)
> +               dpu->core->capability(&dpu->ctx, &cap);
> +
> +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> +                                 sizeof(struct sprd_dpu_layer), GFP_KERNEL);
> +       if (!dpu->layers)
> +               return ERR_PTR(-ENOMEM);
> +
> +       for (i = 0; i < cap.max_layers; i++) {
> +
> +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> +               if (!p)
> +                       return ERR_PTR(-ENOMEM);
> +
> +               err = drm_universal_plane_init(drm, &p->plane, 1,
> +                                              &sprd_plane_funcs, cap.fmts_ptr,
> +                                              cap.fmts_cnt, NULL,
> +                                              DRM_PLANE_TYPE_PRIMARY, NULL);
> +               if (err) {
> +                       DRM_ERROR("fail to init primary plane\n");
> +                       return ERR_PTR(err);
> +               }
> +
> +               drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
> +
> +               sprd_plane_create_properties(p, i);
> +
> +               p->index = i;
> +               if (i == 0)
> +                       primary = &p->plane;
> +       }
> +
> +       if (p)
> +               DRM_INFO("dpu plane init ok\n");
> +
> +       return primary;
> +}
> +
> +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> +       else
> +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> +}
> +
> +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> +                                       const struct drm_display_mode *mode)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
> +
> +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> +               dpu->mode = (struct drm_display_mode *)mode;
> +
> +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> +               dpu->mode = (struct drm_display_mode *)mode;
> +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> +                                  struct drm_crtc_state *old_state)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s()\n", __func__);
> +
> +       sprd_dpu_init(dpu);
> +
> +       enable_irq(dpu->ctx.irq);
> +
> +       drm_crtc_vblank_on(crtc);
> +}
> +
> +static void sprd_crtc_atomic_disable(struct drm_crtc *crtc,
> +                                   struct drm_crtc_state *old_state)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +       struct drm_device *drm = dpu->crtc.dev;
> +
> +       DRM_INFO("%s()\n", __func__);
> +
> +       drm_crtc_vblank_off(crtc);
> +
> +       disable_irq(dpu->ctx.irq);
> +
> +       sprd_dpu_fini(dpu);
> +
> +       spin_lock_irq(&drm->event_lock);
> +       if (crtc->state->event) {
> +               drm_crtc_send_vblank_event(crtc, crtc->state->event);
> +               crtc->state->event = NULL;
> +       }
> +       spin_unlock_irq(&drm->event_lock);
> +}
> +
> +static int sprd_crtc_atomic_check(struct drm_crtc *crtc,
> +                                struct drm_crtc_state *state)
> +{
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       return 0;
> +}
> +
> +static void sprd_crtc_atomic_begin(struct drm_crtc *crtc,
> +                                 struct drm_crtc_state *old_state)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       memset(dpu->layers, 0, sizeof(*dpu->layers) * dpu->pending_planes);
> +
> +       dpu->pending_planes = 0;
> +}
> +
> +static void sprd_crtc_atomic_flush(struct drm_crtc *crtc,
> +                                 struct drm_crtc_state *old_state)
> +
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +       struct drm_device *drm = dpu->crtc.dev;
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       if (dpu->core && dpu->core->flip && dpu->pending_planes)
> +               dpu->core->flip(&dpu->ctx, dpu->layers, dpu->pending_planes);
> +
> +       spin_lock_irq(&drm->event_lock);
> +       if (crtc->state->event) {
> +               drm_crtc_send_vblank_event(crtc, crtc->state->event);
> +               crtc->state->event = NULL;
> +       }
> +       spin_unlock_irq(&drm->event_lock);
> +}
> +
> +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       if (dpu->core && dpu->core->enable_vsync)
> +               dpu->core->enable_vsync(&dpu->ctx);
> +
> +       return 0;
> +}
> +
> +static void sprd_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
> +       if (dpu->core && dpu->core->disable_vsync)
> +               dpu->core->disable_vsync(&dpu->ctx);
> +}
> +
> +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +       struct drm_device *drm = dpu->crtc.dev;
> +       struct drm_property *prop;
> +       struct drm_property_blob *blob;
> +       size_t blob_size;
> +
> +       blob_size = strlen(dpu->ctx.version) + 1;
> +
> +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> +                       dpu->ctx.version);
> +       if (IS_ERR(blob)) {
> +               DRM_ERROR("drm_property_create_blob dpu version failed\n");
> +               return PTR_ERR(blob);
> +       }
> +
> +       /* create dpu version property */
> +       prop = drm_property_create(drm,
> +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> +               "dpu version", 0);
> +       if (!prop) {
> +               DRM_ERROR("drm_property_create dpu version failed\n");
> +               return -ENOMEM;
> +       }
> +       drm_object_attach_property(&crtc->base, prop, blob->base.id);
> +
> +       return 0;
> +}
> +
> +static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = {
> +       .mode_set_nofb  = sprd_crtc_mode_set_nofb,
> +       .mode_valid     = sprd_crtc_mode_valid,
> +       .atomic_check   = sprd_crtc_atomic_check,
> +       .atomic_begin   = sprd_crtc_atomic_begin,
> +       .atomic_flush   = sprd_crtc_atomic_flush,
> +       .atomic_enable  = sprd_crtc_atomic_enable,
> +       .atomic_disable = sprd_crtc_atomic_disable,
> +};
> +
> +static const struct drm_crtc_funcs sprd_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  = sprd_crtc_enable_vblank,
> +       .disable_vblank = sprd_crtc_disable_vblank,
> +};
> +
> +static int sprd_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
> +                        struct drm_plane *primary)
> +{
> +       struct device_node *port;
> +       int err;
> +
> +       /*
> +        * set crtc port so that drm_of_find_possible_crtcs call works
> +        */
> +       port = of_parse_phandle(drm->dev->of_node, "ports", 0);
> +       if (!port) {
> +               DRM_ERROR("find 'ports' phandle of %s failed\n",
> +                         drm->dev->of_node->full_name);
> +               return -EINVAL;
> +       }
> +       of_node_put(port);
> +       crtc->port = port;
> +
> +       err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
> +                                       &sprd_crtc_funcs, NULL);
> +       if (err) {
> +               DRM_ERROR("failed to init crtc.\n");
> +               return err;
> +       }
> +
> +       drm_mode_crtc_set_gamma_size(crtc, 256);
> +
> +       drm_crtc_helper_add(crtc, &sprd_crtc_helper_funcs);
> +
> +       sprd_crtc_create_properties(crtc);
> +
> +       DRM_INFO("%s() ok\n", __func__);
> +       return 0;
> +}
> +
> +static int sprd_dpu_init(struct sprd_dpu *dpu)
> +{
> +       struct dpu_context *ctx = &dpu->ctx;
> +
> +       if (dpu->core && dpu->core->init)
> +               dpu->core->init(ctx);
> +       if (dpu->core && dpu->core->ifconfig)
> +               dpu->core->ifconfig(ctx);
> +
> +       return 0;
> +}
> +
> +static int sprd_dpu_fini(struct sprd_dpu *dpu)
> +{
> +       struct dpu_context *ctx = &dpu->ctx;
> +
> +       if (dpu->core && dpu->core->fini)
> +               dpu->core->fini(ctx);
> +
> +       return 0;
> +}
> +
> +static irqreturn_t sprd_dpu_isr(int irq, void *data)
> +{
> +       struct sprd_dpu *dpu = data;
> +       struct dpu_context *ctx = &dpu->ctx;
> +       u32 int_mask = 0;
> +
> +       if (dpu->core && dpu->core->isr)
> +               int_mask = dpu->core->isr(ctx);
> +
> +       if (int_mask & DISPC_INT_ERR_MASK)
> +               DRM_WARN("Warning: dpu underflow!\n");
> +
> +       if ((int_mask & DISPC_INT_DPI_VSYNC_MASK))
> +               drm_crtc_handle_vblank(&dpu->crtc);
> +
> +       return IRQ_HANDLED;
> +}
> +
> +static int sprd_dpu_irq_request(struct sprd_dpu *dpu)
> +{
> +       int err;
> +       int irq_num;
> +
> +       irq_num = irq_of_parse_and_map(dpu->dev.of_node, 0);

Use platform_get_irq instead.

> +       if (!irq_num) {
> +               DRM_ERROR("error: dpu parse irq num failed\n");
> +               return -EINVAL;
> +       }
> +       DRM_INFO("dpu irq_num = %d\n", irq_num);
> +
> +       irq_set_status_flags(irq_num, IRQ_NOAUTOEN);

I think you shouldn't need this. Make sure you've disabled interrupts
in the h/w first or that the handler can handle it if you haven't.

> +       err = devm_request_irq(&dpu->dev, irq_num, sprd_dpu_isr,
> +                                       0, "DISPC", dpu);
> +       if (err) {
> +               DRM_ERROR("error: dpu request irq failed\n");
> +               return -EINVAL;
> +       }
> +       dpu->ctx.irq = irq_num;
> +
> +       return 0;
> +}
> +
> +static int sprd_dpu_bind(struct device *dev, struct device *master, void *data)
> +{
> +       struct drm_device *drm = data;
> +       struct sprd_dpu *dpu = dev_get_drvdata(dev);
> +       struct drm_plane *plane;
> +       int err;
> +
> +       DRM_INFO("%s()\n", __func__);
> +
> +       plane = sprd_plane_init(drm, dpu);
> +       if (IS_ERR_OR_NULL(plane)) {
> +               err = PTR_ERR(plane);
> +               return err;
> +       }
> +
> +       err = sprd_crtc_init(drm, &dpu->crtc, plane);
> +       if (err)
> +               return err;
> +
> +       sprd_dpu_irq_request(dpu);
> +
> +       return 0;
> +}
> +
> +static void sprd_dpu_unbind(struct device *dev, struct device *master,
> +       void *data)
> +{
> +       struct sprd_dpu *dpu = dev_get_drvdata(dev);
> +
> +       DRM_INFO("%s()\n", __func__);
> +
> +       drm_crtc_cleanup(&dpu->crtc);
> +}
> +
> +static const struct component_ops dpu_component_ops = {
> +       .bind = sprd_dpu_bind,
> +       .unbind = sprd_dpu_unbind,
> +};
> +
> +static int sprd_dpu_context_init(struct sprd_dpu *dpu,
> +                               struct device_node *np)
> +{
> +       struct resource r;
> +       struct dpu_context *ctx = &dpu->ctx;
> +
> +       if (of_address_to_resource(np, 0, &r)) {
> +               DRM_ERROR("parse dt base address failed\n");
> +               return -ENODEV;
> +       }
> +       ctx->base = (unsigned long)ioremap(r.start,
> +                                       resource_size(&r));
> +       if (ctx->base == 0) {
> +               DRM_ERROR("ioremap base address failed\n");
> +               return -EFAULT;
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct sprd_dpu_ops sharkl3_dpu = {
> +       .core = &sharkl3_dpu_core_ops,
> +};
> +
> +static const struct of_device_id dpu_match_table[] = {
> +       { .compatible = "sprd,sharkl3-dpu",
> +         .data = &sharkl3_dpu },
> +       { /* sentinel */ },
> +};
> +
> +static int sprd_dpu_probe(struct platform_device *pdev)
> +{
> +       struct device_node *np = pdev->dev.of_node;
> +       const struct of_device_id *of_id =
> +               of_match_node(dpu_match_table, np);

Don't need this.

> +       const struct sprd_dpu_ops *pdata;
> +       struct sprd_dpu *dpu;
> +       int ret;
> +
> +       dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
> +       if (!dpu)
> +               return -ENOMEM;
> +
> +       pdata = of_device_get_match_data(&pdev->dev);
> +       if (pdata) {
> +               dpu->core = pdata->core;
> +               dpu->ctx.version = "dpu-r2p0";
> +       } else {
> +               DRM_ERROR("Can't get %s ops data\n", of_id->name);
> +               return -EINVAL;
> +       }
> +
> +       ret = sprd_dpu_context_init(dpu, np);
> +       if (ret)
> +               return ret;
> +
> +       platform_set_drvdata(pdev, dpu);
> +
> +       return component_add(&pdev->dev, &dpu_component_ops);
> +}
> +
> +static int sprd_dpu_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &dpu_component_ops);
> +       return 0;
> +}
> +
> +struct platform_driver sprd_dpu_driver = {
> +       .probe = sprd_dpu_probe,
> +       .remove = sprd_dpu_remove,
> +       .driver = {
> +               .name = "sprd-dpu-drv",
> +               .of_match_table = dpu_match_table,
> +       },
> +};
> +
> +MODULE_AUTHOR("Leon He <leon.he@unisoc.com>");
> +MODULE_AUTHOR("Kevin Tang <kevin.tang@unisoc.com>");
> +MODULE_DESCRIPTION("Unisoc Display Controller Driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/sprd/sprd_dpu.h b/drivers/gpu/drm/sprd/sprd_dpu.h
> new file mode 100644
> index 0000000..3e7f91f
> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/sprd_dpu.h
> @@ -0,0 +1,127 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2020 Unisoc Inc.
> + */
> +
> +#ifndef __SPRD_DPU_H__
> +#define __SPRD_DPU_H__
> +
> +#include <linux/bug.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/platform_device.h>
> +#include <linux/string.h>
> +#include <video/videomode.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +#include <uapi/drm/drm_mode.h>
> +#include "disp_lib.h"
> +
> +#define DISPC_INT_DONE_MASK            BIT(0)
> +#define DISPC_INT_TE_MASK              BIT(1)
> +#define DISPC_INT_ERR_MASK             BIT(2)
> +#define DISPC_INT_EDPI_TE_MASK         BIT(3)
> +#define DISPC_INT_UPDATE_DONE_MASK     BIT(4)
> +#define DISPC_INT_DPI_VSYNC_MASK       BIT(5)
> +#define DISPC_INT_WB_DONE_MASK         BIT(6)
> +#define DISPC_INT_WB_FAIL_MASK         BIT(7)
> +
> +/* NOTE: this mask is not a realy dpu interrupt mask */
> +#define DISPC_INT_FENCE_SIGNAL_REQUEST BIT(31)
> +
> +enum {
> +       SPRD_DISPC_IF_DBI = 0,
> +       SPRD_DISPC_IF_DPI,
> +       SPRD_DISPC_IF_EDPI,
> +       SPRD_DISPC_IF_LIMIT
> +};
> +
> +enum {
> +       SPRD_IMG_DATA_ENDIAN_B0B1B2B3 = 0,
> +       SPRD_IMG_DATA_ENDIAN_B3B2B1B0,
> +       SPRD_IMG_DATA_ENDIAN_B2B3B0B1,
> +       SPRD_IMG_DATA_ENDIAN_B1B0B3B2,
> +       SPRD_IMG_DATA_ENDIAN_LIMIT
> +};
> +
> +struct sprd_dpu_layer {
> +       u8 index;
> +       u8 planes;
> +       u32 addr[4];
> +       u32 pitch[4];
> +       s16 src_x;
> +       s16 src_y;
> +       s16 src_w;
> +       s16 src_h;
> +       s16 dst_x;
> +       s16 dst_y;
> +       u16 dst_w;
> +       u16 dst_h;
> +       u32 format;
> +       u32 alpha;
> +       u32 blending;
> +       u32 rotation;
> +};
> +
> +struct dpu_capability {
> +       u32 max_layers;
> +       const u32 *fmts_ptr;
> +       u32 fmts_cnt;
> +};
> +
> +struct dpu_context;
> +
> +struct dpu_core_ops {
> +       int (*init)(struct dpu_context *ctx);
> +       void (*fini)(struct dpu_context *ctx);
> +       void (*run)(struct dpu_context *ctx);
> +       void (*stop)(struct dpu_context *ctx);
> +       void (*disable_vsync)(struct dpu_context *ctx);
> +       void (*enable_vsync)(struct dpu_context *ctx);
> +       u32 (*isr)(struct dpu_context *ctx);
> +       void (*ifconfig)(struct dpu_context *ctx);
> +       void (*flip)(struct dpu_context *ctx,
> +                    struct sprd_dpu_layer layers[], u8 count);
> +       int (*capability)(struct dpu_context *ctx,
> +                       struct dpu_capability *cap);
> +       void (*bg_color)(struct dpu_context *ctx, u32 color);
> +};
> +
> +struct sprd_dpu_ops {
> +       const struct dpu_core_ops *core;
> +};
> +
> +struct dpu_context {
> +       unsigned long base;
> +       const char *version;
> +       int irq;
> +       u8 if_type;
> +       struct videomode vm;
> +       bool stopped;
> +       wait_queue_head_t wait_queue;
> +       bool evt_update;
> +       bool evt_stop;
> +};
> +
> +struct sprd_dpu {
> +       struct device dev;
> +       struct drm_crtc crtc;
> +       struct dpu_context ctx;
> +       const struct dpu_core_ops *core;
> +       struct drm_display_mode *mode;
> +       struct sprd_dpu_layer *layers;
> +       u8 pending_planes;
> +};
> +
> +static inline struct sprd_dpu *crtc_to_dpu(struct drm_crtc *crtc)
> +{
> +       return crtc ? container_of(crtc, struct sprd_dpu, crtc) : NULL;
> +}
> +
> +extern const struct dpu_core_ops sharkl3_dpu_core_ops;
> +
> +#endif
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c
> index 4706185..200020f 100644
> --- a/drivers/gpu/drm/sprd/sprd_drm.c
> +++ b/drivers/gpu/drm/sprd/sprd_drm.c
> @@ -200,6 +200,7 @@ static struct platform_driver sprd_drm_driver = {
>
>  static struct platform_driver *sprd_drm_drivers[]  = {
>         &sprd_drm_driver,
> +       &sprd_dpu_driver,
>  };
>
>  static int __init sprd_drm_init(void)
> diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h
> index edf0881..3c32f3a 100644
> --- a/drivers/gpu/drm/sprd/sprd_drm.h
> +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> @@ -13,4 +13,6 @@ struct sprd_drm {
>         struct drm_device *drm;
>  };
>
> +extern struct platform_driver sprd_dpu_driver;
> +
>  #endif /* _SPRD_DRM_H_ */
> --
> 2.7.4
>
Emil Velikov March 2, 2020, 6:28 p.m. UTC | #2
Hi Kevin,

There's a few small suggestions, although overall the driver looks a lot better.

On Thu, 27 Feb 2020 at 08:14, Kevin Tang <kevin3.tang@gmail.com> wrote:

> --- /dev/null
> +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +ifdef CONFIG_ARM64
> +KBUILD_CFLAGS += -mstrict-align


There are many other drivers that do not use readl/writel for register access,
yet none has this workaround... Even those that they are exclusively ARM64.

Have you tried that it's not a buggy version of GCC? At the very least, I'd
encourage you to add a brief comment about the problem + setup.

... In general I think one should follow the suggestions from Rob Herring.


> +static void dpu_dump(struct dpu_context *ctx)
> +{
> +       u32 *reg = (u32 *)ctx->base;
> +       int i;
> +
> +       pr_info("      0          4          8          C\n");
> +       for (i = 0; i < 256; i += 4) {
> +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);

Using some of the helpers from drm_print.h would be better than pr_*.
This applies for the rest of the patch.


> +static void dpu_clean_all(struct dpu_context *ctx)
> +{
> +       int i;
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       for (i = 0; i < 8; i++)

This "< 8" seem pretty magical. How about "< ARRAY_SIZE(reg->layers)"
Same logic applies through the rest of the patch.


> +static int dpu_wait_stop_done(struct dpu_context *ctx)
> +{
> +       int rc;
> +
> +       if (ctx->stopped)
> +               return 0;
> +
The stopped handling does look suspicious. Admittedly I did not look too closely
at the dpu_flip code, which seems to require it.

Let's add a small comment in the struct dpu_context::stopped declaration, why it
is needed, if it truely is.

> +       rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_stop,
> +                                              msecs_to_jiffies(500));
> +       ctx->evt_stop = false;
> +
> +       ctx->stopped = true;
> +
> +       if (!rc) {
> +               pr_err("dpu wait for stop done time out!\n");
> +               return -ETIMEDOUT;
> +       }
> +
> +       return 0;
> +}
> +

> +static void dpu_stop(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       if (ctx->stopped)
> +               return;
> +
> +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> +               reg->dpu_ctrl |= BIT(1);
> +
> +       dpu_wait_stop_done(ctx);
> +
> +       pr_info("dpu stop\n");

This and the dpu_run pr_info() messages can be removed.


> +}
> +
> +static void dpu_run(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +
> +       if (!ctx->stopped)
> +               return;
> +
> +       reg->dpu_ctrl |= BIT(0);
> +
> +       ctx->stopped = false;
> +
> +       pr_info("dpu run\n");
> +}
> +
> +static int dpu_init(struct dpu_context *ctx)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       u32 size;
> +
> +       reg->bg_color = 0;
> +
> +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> +       reg->panel_size = size;
> +       reg->blend_size = size;
> +
> +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> +
> +       reg->dpu_cfg1 = 0x004466da;
> +       reg->dpu_cfg2 = 0;
> +
> +       if (ctx->stopped)
> +               dpu_clean_all(ctx);
> +
> +       reg->mmu_en = 0;
> +       reg->mmu_min_ppn1 = 0;
> +       reg->mmu_ppn_range1 = 0xffff;
> +       reg->mmu_min_ppn2 = 0;
> +       reg->mmu_ppn_range2 = 0xffff;
> +       reg->mmu_vpn_range = 0x1ffff;
> +
> +       reg->dpu_int_clr = 0xffff;
> +
> +       init_waitqueue_head(&ctx->wait_queue);
> +
> +       return 0;

Function always returns 0. Let's make it static void dpu_init()



> +static u32 to_dpu_rotation(u32 angle)
> +{
> +       u32 rot = DPU_LAYER_ROTATION_0;
> +
> +       switch (angle) {
> +       case 0:
> +       case DRM_MODE_ROTATE_0:
> +               rot = DPU_LAYER_ROTATION_0;
> +               break;
> +       case DRM_MODE_ROTATE_90:
> +               rot = DPU_LAYER_ROTATION_90;
> +               break;
> +       case DRM_MODE_ROTATE_180:
> +               rot = DPU_LAYER_ROTATION_180;
> +               break;
> +       case DRM_MODE_ROTATE_270:
> +               rot = DPU_LAYER_ROTATION_270;
> +               break;
> +       case DRM_MODE_REFLECT_Y:
> +               rot = DPU_LAYER_ROTATION_180_M;
> +               break;
> +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> +               rot = DPU_LAYER_ROTATION_90_M;
> +               break;
> +       case DRM_MODE_REFLECT_X:
> +               rot = DPU_LAYER_ROTATION_0_M;
> +               break;
> +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> +               rot = DPU_LAYER_ROTATION_270_M;
> +               break;
> +       default:
> +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
> +               break;

Have you seen a case where the 0 or default case are reached? AFAICT they will
never trigger. So one might as well use:
    switch (angle) {
    case DRM_MODE_FOO:
        return DPU_LAYER_ROTATION_FOO;
    ...
    case DRM_MODE_BAR:
        return DPU_LAYER_ROTATION_BAR;
    }


> +       }
> +
> +       return rot;
> +}
> +
> +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> +{
> +       int reg_val = 0;
> +
> +       /* layer enable */
> +       reg_val |= BIT(0);
> +
> +       switch (format) {
> +       case DRM_FORMAT_BGRA8888:
> +               /* BGRA8888 -> ARGB8888 */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_RGBX8888:
> +       case DRM_FORMAT_RGBA8888:
> +               /* RGBA8888 -> ABGR8888 */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               /* FALLTHRU */
> +       case DRM_FORMAT_ABGR8888:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_ARGB8888:
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_XBGR8888:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_XRGB8888:
> +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> +               break;
> +       case DRM_FORMAT_BGR565:
> +               /* rb switch */
> +               reg_val |= BIT(10);
> +               /* FALLTHRU */
> +       case DRM_FORMAT_RGB565:
> +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> +               break;
> +       case DRM_FORMAT_NV12:
> +               /* 2-Lane: Yuv420 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_NV21:
> +               /* 2-Lane: Yuv420 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       case DRM_FORMAT_NV16:
> +               /* 2-Lane: Yuv422 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       case DRM_FORMAT_NV61:
> +               /* 2-Lane: Yuv422 */
> +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_YUV420:
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> +               break;
> +       case DRM_FORMAT_YVU420:
> +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> +               /* Y endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> +               /* UV endian */
> +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> +               break;
> +       default:
> +               pr_err("error: invalid format %c%c%c%c\n", format,
> +                                               format >> 8,
> +                                               format >> 16,
> +                                               format >> 24);
> +               break;
The default case here should be unreachable. Either it is or the upper layer (or
earlier code) should ensure that.

> +       }
> +
> +       switch (blending) {
> +       case DRM_MODE_BLEND_PIXEL_NONE:
> +               /* don't do blending, maybe RGBX */
> +               /* alpha mode select - layer alpha */
> +               reg_val |= BIT(2);
> +               break;
> +       case DRM_MODE_BLEND_COVERAGE:
> +               /* alpha mode select - combo alpha */
> +               reg_val |= BIT(3);
> +               /*Normal mode*/
> +               reg_val &= (~BIT(16));
> +               break;
> +       case DRM_MODE_BLEND_PREMULTI:
> +               /* alpha mode select - combo alpha */
> +               reg_val |= BIT(3);
> +               /*Pre-mult mode*/
> +               reg_val |= BIT(16);
> +               break;
> +       default:
> +               /* alpha mode select - layer alpha */
> +               reg_val |= BIT(2);
> +               break;
Ditto

> +       }
> +
> +       rotation = to_dpu_rotation(rotation);
> +       reg_val |= (rotation & 0x7) << 20;
> +
> +       return reg_val;
> +}
> +

> +static void dpu_layer(struct dpu_context *ctx,
> +                   struct sprd_dpu_layer *hwlayer)
> +{
> +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> +       const struct drm_format_info *info;
> +       struct layer_reg *layer;
> +       u32 addr, size, offset;
> +       int i;
> +
> +       layer = &reg->layers[hwlayer->index];
> +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> +
> +       if (hwlayer->src_w && hwlayer->src_h)
> +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
> +       else
> +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
> +
> +       for (i = 0; i < hwlayer->planes; i++) {
> +               addr = hwlayer->addr[i];
> +
> +               if (addr % 16)
> +                       pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
> +                               i, addr);
> +               layer->addr[i] = addr;
> +       }
> +
> +       layer->pos = offset;
> +       layer->size = size;
> +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> +       layer->alpha = hwlayer->alpha;
> +
> +       info = drm_format_info(hwlayer->format);
> +       if (info->cpp[0] == 0) {

Ditto

> +               pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
> +               return;
> +       }
> +




> +static int dpu_capability(struct dpu_context *ctx,
> +                       struct dpu_capability *cap)
> +{
> +       if (!cap)
> +               return -EINVAL;
> +
Ensure the caller always passes cap != NULL and drop the function return type?

> +       cap->max_layers = 6;
> +       cap->fmts_ptr = primary_fmts;
> +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> +
> +       return 0;
> +}


> +static int sprd_plane_atomic_check(struct drm_plane *plane,
> +                                 struct drm_plane_state *state)
> +{
> +       DRM_DEBUG("%s()\n", __func__);
> +

Would be nice to hear from the atomic experts, how a no-op atomic_check goes
with the overall atomic semantics.


> +       return 0;
> +}
> +


> +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> +                                    struct drm_plane_state *old_state)
> +{
> +       struct sprd_plane *p = to_sprd_plane(plane);
> +
> +       /*
> +        * NOTE:
> +        * The dpu->core->flip() will disable all the planes each time.
> +        * So there is no need to impliment the atomic_disable() function.
> +        * But this function can not be removed, because it will change
> +        * to call atomic_update() callback instead. Which will cause
> +        * kernel panic in sprd_plane_atomic_update().
> +        *
> +        * We do nothing here but just print a debug log.
> +        */
> +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);

Similar to the check - would be nice to see a confirmation, that this isn't
abusing atomics in some way.


> +}
> +
> +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> +{
> +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> +
> +       /* create rotation property */
> +       drm_plane_create_rotation_property(&p->plane,
> +                                          DRM_MODE_ROTATE_0,
> +                                          DRM_MODE_ROTATE_MASK |
> +                                          DRM_MODE_REFLECT_MASK);
> +
> +       /* create alpha property */
> +       drm_plane_create_alpha_property(&p->plane);
> +
> +       /* create blend mode property */
> +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> +
> +       /* create zpos property */
> +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> +
Either check if creating the properties fail (and propagate the error) or drop
the function return type. As-is it's in the middle making it fairly misleading.

> +       return 0;
> +}
> +


> +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> +                                       struct sprd_dpu *dpu)
> +{
> +       struct drm_plane *primary = NULL;
> +       struct sprd_plane *p = NULL;
> +       struct dpu_capability cap = {};
> +       int err, i;
> +
> +       if (dpu->core && dpu->core->capability)
As mentioned before - this always evaluates to true, so drop the check.
Same applies for the other dpu->core->foo checks.

Still not a huge fan of the abstraction layer, but I guess you're hesitant on
removing it.

> +               dpu->core->capability(&dpu->ctx, &cap);
> +
> +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> +                                 sizeof(struct sprd_dpu_layer), GFP_KERNEL);
> +       if (!dpu->layers)
> +               return ERR_PTR(-ENOMEM);
> +
> +       for (i = 0; i < cap.max_layers; i++) {
> +
> +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> +               if (!p)
> +                       return ERR_PTR(-ENOMEM);
> +
> +               err = drm_universal_plane_init(drm, &p->plane, 1,
> +                                              &sprd_plane_funcs, cap.fmts_ptr,
> +                                              cap.fmts_cnt, NULL,
> +                                              DRM_PLANE_TYPE_PRIMARY, NULL);
> +               if (err) {
> +                       DRM_ERROR("fail to init primary plane\n");
> +                       return ERR_PTR(err);
> +               }
> +
> +               drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
> +
> +               sprd_plane_create_properties(p, i);
> +
> +               p->index = i;
> +               if (i == 0)
> +                       primary = &p->plane;
> +       }
> +
> +       if (p)
> +               DRM_INFO("dpu plane init ok\n");

This and nearly all the other DRM_INFO() messages look like a debug/development
left over. Please remove them - the driver does not need to print when functions
are successfull.


> +
> +       return primary;
> +}
> +
> +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> +       else
> +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> +}
> +
> +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> +                                       const struct drm_display_mode *mode)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
> +

If needed, let's move this to core and make it a debug message. As-is it will
cause spam for no reason.


> +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> +               dpu->mode = (struct drm_display_mode *)mode;
> +
> +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> +               dpu->mode = (struct drm_display_mode *)mode;

Casting away the constness is a bad idea.

Instead, let's move the if_type decision here, thus we can remove the
nsprd_crtc_mode_set_nofb function? This way we can also remove sprd_dpu::mode.


> +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);

Similarly, one could derive the vm based attributes here and remove dpu->ctx.vm.


> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> +                                  struct drm_crtc_state *old_state)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s()\n", __func__);
> +
More sprurious info messages - debug leftover?



> +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
Personally, I don't see the appeal in these debug messages. While a few display
controllers have the odd piece, they are an exception in DRM.



> +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +       struct drm_device *drm = dpu->crtc.dev;
> +       struct drm_property *prop;
> +       struct drm_property_blob *blob;
> +       size_t blob_size;
> +
> +       blob_size = strlen(dpu->ctx.version) + 1;
> +
> +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> +                       dpu->ctx.version);
> +       if (IS_ERR(blob)) {
> +               DRM_ERROR("drm_property_create_blob dpu version failed\n");
> +               return PTR_ERR(blob);
> +       }
> +
> +       /* create dpu version property */
> +       prop = drm_property_create(drm,
> +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> +               "dpu version", 0);

Note: Custom properties should be separate patches. This includes documentation
why they are needed and references to open-source userspace.


HTH
Emil
Kevin Tang March 4, 2020, 4:56 a.m. UTC | #3
Hi Emil,
Thanks for your feedback

On Tue, Mar 3, 2020 at 2:29 AM Emil Velikov <emil.l.velikov@gmail.com>
wrote:

> Hi Kevin,
>
> There's a few small suggestions, although overall the driver looks a lot
> better.
>
> On Thu, 27 Feb 2020 at 08:14, Kevin Tang <kevin3.tang@gmail.com> wrote:
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> > @@ -0,0 +1,7 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ifdef CONFIG_ARM64
> > +KBUILD_CFLAGS += -mstrict-align
>
>
> There are many other drivers that do not use readl/writel for register
> access,
> yet none has this workaround... Even those that they are exclusively ARM64.
>
> Have you tried that it's not a buggy version of GCC? At the very least, I'd
> encourage you to add a brief comment about the problem + setup.
>
> ... In general I think one should follow the suggestions from Rob Herring.
>
Yocto v2.5

aarch64-linaro-linux-gcc (Linaro GCC 7.2-2017.11) 7.2.1 20171011


/sprd/drv/dispc/dpu_r2p0.c:729

1796256 ffffff8008486650:       f803c043        stur    x3, [x2,#60]
=>Unhandled
fault: alignment fault (0x96000061) at 0xffffff80098b883c


The following C code operation are continuous. The compiler may think that
the access can be completed by directly using two 64-bit assignment
operations, so it is optimized to 64-bit operation.


729         reg->mmu_min_ppn1 = 0;

730         reg->mmu_ppn_range1 = 0xffff;

731         reg->mmu_min_ppn2 = 0;

732         reg->mmu_ppn_range2 = 0xffff;



=============================

Yocto v2.5

aarch64-linaro-linux-gcc (Linaro GCC 7.2-2017.11) 7.2.1 20171011



/sprd/drv/dispc/dpu_r2p0.c:729

1796244 ffffff8008486638:       91200022        add     x2, x1, #0x800

/sprd/drv/dispc/dpu_r2p0.c:728

1796246 ffffff800848663c:       b908003f        str     wzr, [x1,#2048]

/sprd/drv/dispc/dpu_r2p0.c:729

1796248 ffffff8008486640:       d2dfffe3        mov     x3,
#0xffff00000000

/sprd/drv/dispc/dpu_r2p0.c:733

1796250 ffffff8008486644:       12bfffc4        mov     w4,
#0x1ffff

/sprd/drv/dispc/dpu_r2p0.c:735

1796252 ffffff8008486648:       529fffe5        mov     w5,
#0xffff

/sprd/drv/dispc/dpu_r2p0.c:741

1796254 ffffff800848664c:       52800000        mov     w0,
#0x0

/sprd/drv/dispc/dpu_r2p0.c:729

1796256 ffffff8008486650:       f803c043        stur    x3, [x2,#60]
=>Unhandled
fault: alignment fault (0x96000061) at 0xffffff80098b883c

/sprd/drv/dispc/dpu_r2p0.c:730

1796258 ffffff8008486654:       f8044043        stur    x3, [x2,#68]

/sprd/drv/dispc/dpu_r2p0.c:735

1796260 ffffff8008486658:       b901e425        str     w5, [x1,#484]

/sprd/drv/dispc/dpu_r2p0.c:733

1796262 ffffff800848665c:       b9080c24        str     w4, [x1,#2060]

1796263 ffffff8008486660:       f9400274        ldr     x20, [x19]

>
>
> > +static void dpu_dump(struct dpu_context *ctx)
> > +{
> > +       u32 *reg = (u32 *)ctx->base;
> > +       int i;
> > +
> > +       pr_info("      0          4          8          C\n");
> > +       for (i = 0; i < 256; i += 4) {
> > +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> > +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i +
> 3]);
>
> Using some of the helpers from drm_print.h would be better than pr_*.
> This applies for the rest of the patch.
>
>
> > +static void dpu_clean_all(struct dpu_context *ctx)
> > +{
> > +       int i;
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       for (i = 0; i < 8; i++)
>
> This "< 8" seem pretty magical. How about "< ARRAY_SIZE(reg->layers)"
> Same logic applies through the rest of the patch.
>
>
> > +static int dpu_wait_stop_done(struct dpu_context *ctx)
> > +{
> > +       int rc;
> > +
> > +       if (ctx->stopped)
> > +               return 0;
> > +
> The stopped handling does look suspicious. Admittedly I did not look too
> closely
> at the dpu_flip code, which seems to require it.
>
> Let's add a small comment in the struct dpu_context::stopped declaration,
> why it
> is needed, if it truely is.
>
> > +       rc = wait_event_interruptible_timeout(ctx->wait_queue,
> ctx->evt_stop,
> > +                                              msecs_to_jiffies(500));
> > +       ctx->evt_stop = false;
> > +
> > +       ctx->stopped = true;
> > +
> > +       if (!rc) {
> > +               pr_err("dpu wait for stop done time out!\n");
> > +               return -ETIMEDOUT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
>
> > +static void dpu_stop(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (ctx->stopped)
> > +               return;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> > +               reg->dpu_ctrl |= BIT(1);
> > +
> > +       dpu_wait_stop_done(ctx);
> > +
> > +       pr_info("dpu stop\n");
>
> This and the dpu_run pr_info() messages can be removed.
>
>
> > +}
> > +
> > +static void dpu_run(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (!ctx->stopped)
> > +               return;
> > +
> > +       reg->dpu_ctrl |= BIT(0);
> > +
> > +       ctx->stopped = false;
> > +
> > +       pr_info("dpu run\n");
> > +}
> > +
> > +static int dpu_init(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 size;
> > +
> > +       reg->bg_color = 0;
> > +
> > +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> > +       reg->panel_size = size;
> > +       reg->blend_size = size;
> > +
> > +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> > +
> > +       reg->dpu_cfg1 = 0x004466da;
> > +       reg->dpu_cfg2 = 0;
> > +
> > +       if (ctx->stopped)
> > +               dpu_clean_all(ctx);
> > +
> > +       reg->mmu_en = 0;
> > +       reg->mmu_min_ppn1 = 0;
> > +       reg->mmu_ppn_range1 = 0xffff;
> > +       reg->mmu_min_ppn2 = 0;
> > +       reg->mmu_ppn_range2 = 0xffff;
> > +       reg->mmu_vpn_range = 0x1ffff;
> > +
> > +       reg->dpu_int_clr = 0xffff;
> > +
> > +       init_waitqueue_head(&ctx->wait_queue);
> > +
> > +       return 0;
>
> Function always returns 0. Let's make it static void dpu_init()
>
>
>
> > +static u32 to_dpu_rotation(u32 angle)
> > +{
> > +       u32 rot = DPU_LAYER_ROTATION_0;
> > +
> > +       switch (angle) {
> > +       case 0:
> > +       case DRM_MODE_ROTATE_0:
> > +               rot = DPU_LAYER_ROTATION_0;
> > +               break;
> > +       case DRM_MODE_ROTATE_90:
> > +               rot = DPU_LAYER_ROTATION_90;
> > +               break;
> > +       case DRM_MODE_ROTATE_180:
> > +               rot = DPU_LAYER_ROTATION_180;
> > +               break;
> > +       case DRM_MODE_ROTATE_270:
> > +               rot = DPU_LAYER_ROTATION_270;
> > +               break;
> > +       case DRM_MODE_REFLECT_Y:
> > +               rot = DPU_LAYER_ROTATION_180_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_90_M;
> > +               break;
> > +       case DRM_MODE_REFLECT_X:
> > +               rot = DPU_LAYER_ROTATION_0_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_270_M;
> > +               break;
> > +       default:
> > +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n",
> angle);
> > +               break;
>
> Have you seen a case where the 0 or default case are reached? AFAICT they
> will
> never trigger. So one might as well use:
>     switch (angle) {
>     case DRM_MODE_FOO:
>         return DPU_LAYER_ROTATION_FOO;
>     ...
>     case DRM_MODE_BAR:
>         return DPU_LAYER_ROTATION_BAR;
>     }
>
>
> > +       }
> > +
> > +       return rot;
> > +}
> > +
> > +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> > +{
> > +       int reg_val = 0;
> > +
> > +       /* layer enable */
> > +       reg_val |= BIT(0);
> > +
> > +       switch (format) {
> > +       case DRM_FORMAT_BGRA8888:
> > +               /* BGRA8888 -> ARGB8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_RGBX8888:
> > +       case DRM_FORMAT_RGBA8888:
> > +               /* RGBA8888 -> ABGR8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ABGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ARGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_XBGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_XRGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_BGR565:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_RGB565:
> > +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> > +               break;
> > +       case DRM_FORMAT_NV12:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV21:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV16:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV61:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YUV420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YVU420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       default:
> > +               pr_err("error: invalid format %c%c%c%c\n", format,
> > +                                               format >> 8,
> > +                                               format >> 16,
> > +                                               format >> 24);
> > +               break;
> The default case here should be unreachable. Either it is or the upper
> layer (or
> earlier code) should ensure that.
>
> > +       }
> > +
> > +       switch (blending) {
> > +       case DRM_MODE_BLEND_PIXEL_NONE:
> > +               /* don't do blending, maybe RGBX */
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> > +       case DRM_MODE_BLEND_COVERAGE:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Normal mode*/
> > +               reg_val &= (~BIT(16));
> > +               break;
> > +       case DRM_MODE_BLEND_PREMULTI:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Pre-mult mode*/
> > +               reg_val |= BIT(16);
> > +               break;
> > +       default:
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> Ditto
>
> > +       }
> > +
> > +       rotation = to_dpu_rotation(rotation);
> > +       reg_val |= (rotation & 0x7) << 20;
> > +
> > +       return reg_val;
> > +}
> > +
>
> > +static void dpu_layer(struct dpu_context *ctx,
> > +                   struct sprd_dpu_layer *hwlayer)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       const struct drm_format_info *info;
> > +       struct layer_reg *layer;
> > +       u32 addr, size, offset;
> > +       int i;
> > +
> > +       layer = &reg->layers[hwlayer->index];
> > +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> > +
> > +       if (hwlayer->src_w && hwlayer->src_h)
> > +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) <<
> 16);
> > +       else
> > +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) <<
> 16);
> > +
> > +       for (i = 0; i < hwlayer->planes; i++) {
> > +               addr = hwlayer->addr[i];
> > +
> > +               if (addr % 16)
> > +                       pr_err("layer addr[%d] is not 16 bytes align,
> it's 0x%08x\n",
> > +                               i, addr);
> > +               layer->addr[i] = addr;
> > +       }
> > +
> > +       layer->pos = offset;
> > +       layer->size = size;
> > +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> > +       layer->alpha = hwlayer->alpha;
> > +
> > +       info = drm_format_info(hwlayer->format);
> > +       if (info->cpp[0] == 0) {
>
> Ditto
>
> > +               pr_err("layer[%d] bytes per pixel is invalid\n",
> hwlayer->index);
> > +               return;
> > +       }
> > +
>
>
>
>
> > +static int dpu_capability(struct dpu_context *ctx,
> > +                       struct dpu_capability *cap)
> > +{
> > +       if (!cap)
> > +               return -EINVAL;
> > +
> Ensure the caller always passes cap != NULL and drop the function return
> type?
>
> > +       cap->max_layers = 6;
> > +       cap->fmts_ptr = primary_fmts;
> > +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> > +
> > +       return 0;
> > +}
>
>
> > +static int sprd_plane_atomic_check(struct drm_plane *plane,
> > +                                 struct drm_plane_state *state)
> > +{
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
>
> Would be nice to hear from the atomic experts, how a no-op atomic_check
> goes
> with the overall atomic semantics.
>
>
> > +       return 0;
> > +}
> > +
>
>
> > +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> > +                                    struct drm_plane_state *old_state)
> > +{
> > +       struct sprd_plane *p = to_sprd_plane(plane);
> > +
> > +       /*
> > +        * NOTE:
> > +        * The dpu->core->flip() will disable all the planes each time.
> > +        * So there is no need to impliment the atomic_disable()
> function.
> > +        * But this function can not be removed, because it will change
> > +        * to call atomic_update() callback instead. Which will cause
> > +        * kernel panic in sprd_plane_atomic_update().
> > +        *
> > +        * We do nothing here but just print a debug log.
> > +        */
> > +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
>
> Similar to the check - would be nice to see a confirmation, that this isn't
> abusing atomics in some way.
>
>
> > +}
> > +
> > +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> > +{
> > +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> > +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> > +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> > +
> > +       /* create rotation property */
> > +       drm_plane_create_rotation_property(&p->plane,
> > +                                          DRM_MODE_ROTATE_0,
> > +                                          DRM_MODE_ROTATE_MASK |
> > +                                          DRM_MODE_REFLECT_MASK);
> > +
> > +       /* create alpha property */
> > +       drm_plane_create_alpha_property(&p->plane);
> > +
> > +       /* create blend mode property */
> > +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> > +
> > +       /* create zpos property */
> > +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> > +
> Either check if creating the properties fail (and propagate the error) or
> drop
> the function return type. As-is it's in the middle making it fairly
> misleading.
>
> > +       return 0;
> > +}
> > +
>
>
> > +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> > +                                       struct sprd_dpu *dpu)
> > +{
> > +       struct drm_plane *primary = NULL;
> > +       struct sprd_plane *p = NULL;
> > +       struct dpu_capability cap = {};
> > +       int err, i;
> > +
> > +       if (dpu->core && dpu->core->capability)
> As mentioned before - this always evaluates to true, so drop the check.
> Same applies for the other dpu->core->foo checks.
>
> Still not a huge fan of the abstraction layer, but I guess you're hesitant
> on
> removing it.
>
> > +               dpu->core->capability(&dpu->ctx, &cap);
> > +
> > +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> > +                                 sizeof(struct sprd_dpu_layer),
> GFP_KERNEL);
> > +       if (!dpu->layers)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       for (i = 0; i < cap.max_layers; i++) {
> > +
> > +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> > +               if (!p)
> > +                       return ERR_PTR(-ENOMEM);
> > +
> > +               err = drm_universal_plane_init(drm, &p->plane, 1,
> > +                                              &sprd_plane_funcs,
> cap.fmts_ptr,
> > +                                              cap.fmts_cnt, NULL,
> > +                                              DRM_PLANE_TYPE_PRIMARY,
> NULL);
> > +               if (err) {
> > +                       DRM_ERROR("fail to init primary plane\n");
> > +                       return ERR_PTR(err);
> > +               }
> > +
> > +               drm_plane_helper_add(&p->plane,
> &sprd_plane_helper_funcs);
> > +
> > +               sprd_plane_create_properties(p, i);
> > +
> > +               p->index = i;
> > +               if (i == 0)
> > +                       primary = &p->plane;
> > +       }
> > +
> > +       if (p)
> > +               DRM_INFO("dpu plane init ok\n");
>
> This and nearly all the other DRM_INFO() messages look like a
> debug/development
> left over. Please remove them - the driver does not need to print when
> functions
> are successfull.


>
> > +
> > +       return primary;
> > +}
> > +
> > +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> > +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> > +       else
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> > +}
> > +
> > +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> > +                                       const struct drm_display_mode
> *mode)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__,
> DRM_MODE_ARG(mode));
> > +
>
> If needed, let's move this to core and make it a debug message. As-is it
> will
> cause spam for no reason.
>
>
> > +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> > +               dpu->mode = (struct drm_display_mode *)mode;
> > +
> > +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> > +               dpu->mode = (struct drm_display_mode *)mode;
>
> Casting away the constness is a bad idea.
>
> Instead, let's move the if_type decision here, thus we can remove the
> nsprd_crtc_mode_set_nofb function? This way we can also remove
> sprd_dpu::mode.
>
>
> > +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
>
> Similarly, one could derive the vm based attributes here and remove
> dpu->ctx.vm.
>
>
> > +       }
> > +
> > +       return MODE_OK;
> > +}
> > +
> > +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> > +                                  struct drm_crtc_state *old_state)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> More sprurious info messages - debug leftover?
>
>
>
> > +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> Personally, I don't see the appeal in these debug messages. While a few
> display
> controllers have the odd piece, they are an exception in DRM.
>
>
>
> > +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +       struct drm_device *drm = dpu->crtc.dev;
> > +       struct drm_property *prop;
> > +       struct drm_property_blob *blob;
> > +       size_t blob_size;
> > +
> > +       blob_size = strlen(dpu->ctx.version) + 1;
> > +
> > +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> > +                       dpu->ctx.version);
> > +       if (IS_ERR(blob)) {
> > +               DRM_ERROR("drm_property_create_blob dpu version
> failed\n");
> > +               return PTR_ERR(blob);
> > +       }
> > +
> > +       /* create dpu version property */
> > +       prop = drm_property_create(drm,
> > +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> > +               "dpu version", 0);
>
> Note: Custom properties should be separate patches. This includes
> documentation
> why they are needed and references to open-source userspace.
>
>
> HTH
> Emil
>
Kevin Tang March 4, 2020, 11:45 a.m. UTC | #4
On Fri, Feb 28, 2020 at 4:37 AM Rob Herring <robh+dt@kernel.org> wrote:

> On Wed, Feb 26, 2020 at 3:46 AM Kevin Tang <kevin3.tang@gmail.com> wrote:
> >
> > Adds DPU(Display Processor Unit) support for the Unisoc's display
> subsystem.
> > It's support multi planes, scaler, rotation, PQ(Picture Quality) and
> more.
> >
> > Cc: Orson Zhai <orsonzhai@gmail.com>
> > Cc: Baolin Wang <baolin.wang@linaro.org>
> > Cc: Chunyan Zhang <zhang.lyra@gmail.com>
> > Signed-off-by: Kevin Tang <kevin.tang@unisoc.com>
> > ---
> >  drivers/gpu/drm/sprd/Makefile       |   5 +-
> >  drivers/gpu/drm/sprd/dpu/Makefile   |   7 +
> >  drivers/gpu/drm/sprd/dpu/dpu_r2p0.c | 770
> ++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/sprd/sprd_dpu.c     | 586 +++++++++++++++++++++++++++
> >  drivers/gpu/drm/sprd/sprd_dpu.h     | 127 ++++++
> >  drivers/gpu/drm/sprd/sprd_drm.c     |   1 +
> >  drivers/gpu/drm/sprd/sprd_drm.h     |   2 +
> >  7 files changed, 1497 insertions(+), 1 deletion(-)
> >  create mode 100644 drivers/gpu/drm/sprd/dpu/Makefile
> >  create mode 100644 drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.c
> >  create mode 100644 drivers/gpu/drm/sprd/sprd_dpu.h
> >
> > diff --git a/drivers/gpu/drm/sprd/Makefile
> b/drivers/gpu/drm/sprd/Makefile
> > index 86d95d9..88ab32a 100644
> > --- a/drivers/gpu/drm/sprd/Makefile
> > +++ b/drivers/gpu/drm/sprd/Makefile
> > @@ -2,4 +2,7 @@
> >
> >  subdir-ccflags-y += -I$(srctree)/$(src)
> >
> > -obj-y := sprd_drm.o
> > +obj-y := sprd_drm.o \
> > +       sprd_dpu.o
> > +
> > +obj-y += dpu/
> > diff --git a/drivers/gpu/drm/sprd/dpu/Makefile
> b/drivers/gpu/drm/sprd/dpu/Makefile
> > new file mode 100644
> > index 0000000..73bd497
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> > @@ -0,0 +1,7 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ifdef CONFIG_ARM64
> > +KBUILD_CFLAGS += -mstrict-align
> > +endif
> > +
> > +obj-y += dpu_r2p0.o
> > diff --git a/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> > new file mode 100644
> > index 0000000..984fa9b
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
> > @@ -0,0 +1,770 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2020 Unisoc Inc.
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/wait.h>
> > +#include <linux/workqueue.h>
> > +#include "sprd_dpu.h"
> > +
> > +#define DISPC_INT_FBC_PLD_ERR_MASK     BIT(8)
> > +#define DISPC_INT_FBC_HDR_ERR_MASK     BIT(9)
> > +
> > +#define DISPC_INT_MMU_INV_WR_MASK      BIT(19)
> > +#define DISPC_INT_MMU_INV_RD_MASK      BIT(18)
> > +#define DISPC_INT_MMU_VAOR_WR_MASK     BIT(17)
> > +#define DISPC_INT_MMU_VAOR_RD_MASK     BIT(16)
> > +
> > +struct layer_reg {
> > +       u32 addr[4];
> > +       u32 ctrl;
> > +       u32 size;
> > +       u32 pitch;
> > +       u32 pos;
> > +       u32 alpha;
> > +       u32 ck;
> > +       u32 pallete;
> > +       u32 crop_start;
> > +};
> > +
> > +struct wb_region_reg {
> > +       u32 pos;
> > +       u32 size;
> > +};
> > +
> > +/* dpu controller register */
> > +struct dpu_reg {
>
> structs for registers is not normal coding style. #defines of offsets
> is. More below.
>
> > +       u32 dpu_version;
> > +       u32 dpu_ctrl;
> > +       u32 dpu_cfg0;
> > +       u32 dpu_cfg1;
> > +       u32 dpu_cfg2;
> > +       u32 dpu_secure;
> > +       u32 reserved_0x0018_0x001C[2];
> > +       u32 panel_size;
> > +       u32 blend_size;
> > +       u32 reserved_0x0028;
> > +       u32 bg_color;
> > +       struct layer_reg layers[8];
> > +       u32 wb_base_addr;
> > +       u32 wb_ctrl;
> > +       u32 wb_cfg;
> > +       u32 wb_pitch;
> > +       struct wb_region_reg region[3];
> > +       u32 reserved_0x01D8_0x01DC[2];
> > +       u32 dpu_int_en;
> > +       u32 dpu_int_clr;
> > +       u32 dpu_int_sts;
> > +       u32 dpu_int_raw;
> > +       u32 dpi_ctrl;
> > +       u32 dpi_h_timing;
> > +       u32 dpi_v_timing;
> > +       u32 reserved_0x01FC;
> > +       u32 dpu_enhance_cfg;
> > +       u32 reserved_0x0204_0x020C[3];
> > +       u32 epf_epsilon;
> > +       u32 epf_gain0_3;
> > +       u32 epf_gain4_7;
> > +       u32 epf_diff;
> > +       u32 reserved_0x0220_0x023C[8];
> > +       u32 hsv_lut_addr;
> > +       u32 hsv_lut_wdata;
> > +       u32 hsv_lut_rdata;
> > +       u32 reserved_0x024C_0x027C[13];
> > +       u32 cm_coef01_00;
> > +       u32 cm_coef03_02;
> > +       u32 cm_coef11_10;
> > +       u32 cm_coef13_12;
> > +       u32 cm_coef21_20;
> > +       u32 cm_coef23_22;
> > +       u32 reserved_0x0298_0x02BC[10];
> > +       u32 slp_cfg0;
> > +       u32 slp_cfg1;
> > +       u32 reserved_0x02C8_0x02FC[14];
> > +       u32 gamma_lut_addr;
> > +       u32 gamma_lut_wdata;
> > +       u32 gamma_lut_rdata;
> > +       u32 reserved_0x030C_0x033C[13];
> > +       u32 checksum_en;
> > +       u32 checksum0_start_pos;
> > +       u32 checksum0_end_pos;
> > +       u32 checksum1_start_pos;
> > +       u32 checksum1_end_pos;
> > +       u32 checksum0_result;
> > +       u32 checksum1_result;
> > +       u32 reserved_0x035C;
> > +       u32 dpu_sts[18];
> > +       u32 reserved_0x03A8_0x03AC[2];
> > +       u32 dpu_fbc_cfg0;
> > +       u32 dpu_fbc_cfg1;
> > +       u32 reserved_0x03B8_0x03EC[14];
> > +       u32 rf_ram_addr;
> > +       u32 rf_ram_rdata_low;
> > +       u32 rf_ram_rdata_high;
> > +       u32 reserved_0x03FC_0x07FC[257];
> > +       u32 mmu_en;
> > +       u32 mmu_update;
> > +       u32 mmu_min_vpn;
> > +       u32 mmu_vpn_range;
> > +       u32 mmu_pt_addr;
> > +       u32 mmu_default_page;
> > +       u32 mmu_vaor_addr_rd;
> > +       u32 mmu_vaor_addr_wr;
> > +       u32 mmu_inv_addr_rd;
> > +       u32 mmu_inv_addr_wr;
> > +       u32 mmu_uns_addr_rd;
> > +       u32 mmu_uns_addr_wr;
> > +       u32 mmu_miss_cnt;
> > +       u32 mmu_pt_update_qos;
> > +       u32 mmu_version;
> > +       u32 mmu_min_ppn1;
> > +       u32 mmu_ppn_range1;
> > +       u32 mmu_min_ppn2;
> > +       u32 mmu_ppn_range2;
> > +       u32 mmu_vpn_paor_rd;
> > +       u32 mmu_vpn_paor_wr;
> > +       u32 mmu_ppn_paor_rd;
> > +       u32 mmu_ppn_paor_wr;
> > +       u32 mmu_reg_au_manage;
> > +       u32 mmu_page_rd_ch;
> > +       u32 mmu_page_wr_ch;
> > +       u32 mmu_read_page_cmd_cnt;
> > +       u32 mmu_read_page_latency_cnt;
> > +       u32 mmu_page_max_latency;
> > +};
> > +
> > +static void dpu_dump(struct dpu_context *ctx)
> > +{
> > +       u32 *reg = (u32 *)ctx->base;
> > +       int i;
> > +
> > +       pr_info("      0          4          8          C\n");
> > +       for (i = 0; i < 256; i += 4) {
> > +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> > +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i +
> 3]);
> > +       }
> > +}
> > +
> > +static u32 check_mmu_isr(struct dpu_context *ctx, u32 reg_val)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 mmu_mask = DISPC_INT_MMU_VAOR_RD_MASK |
> > +                       DISPC_INT_MMU_VAOR_WR_MASK |
> > +                       DISPC_INT_MMU_INV_RD_MASK |
> > +                       DISPC_INT_MMU_INV_WR_MASK;
> > +       u32 val = reg_val & mmu_mask;
> > +
> > +       if (val) {
> > +               pr_err("--- iommu interrupt err: 0x%04x ---\n", val);
> > +
> > +               pr_err("iommu invalid read error, addr: 0x%08x\n",
> > +                       reg->mmu_inv_addr_rd);
> > +               pr_err("iommu invalid write error, addr: 0x%08x\n",
> > +                       reg->mmu_inv_addr_wr);
> > +               pr_err("iommu va out of range read error, addr:
> 0x%08x\n",
> > +                       reg->mmu_vaor_addr_rd);
> > +               pr_err("iommu va out of range write error, addr:
> 0x%08x\n",
> > +                       reg->mmu_vaor_addr_wr);
> > +               pr_err("BUG: iommu failure at %s:%d/%s()!\n",
> > +                       __FILE__, __LINE__, __func__);
> > +
> > +               dpu_dump(ctx);
> > +       }
> > +
> > +       return val;
> > +}
> > +
> > +static void dpu_clean_all(struct dpu_context *ctx)
> > +{
> > +       int i;
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       for (i = 0; i < 8; i++)
> > +               reg->layers[i].ctrl = 0;
> > +}
> > +
> > +static u32 dpu_isr(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 reg_val, int_mask = 0;
> > +
> > +       reg_val = reg->dpu_int_sts;
> > +
> > +       /* disable err interrupt */
> > +       if (reg_val & DISPC_INT_ERR_MASK)
> > +               int_mask |= DISPC_INT_ERR_MASK;
> > +
> > +       /* dpu update done isr */
> > +       if (reg_val & DISPC_INT_UPDATE_DONE_MASK) {
> > +               ctx->evt_update = true;
> > +               wake_up_interruptible_all(&ctx->wait_queue);
> > +       }
> > +
> > +       /* dpu stop done isr */
> > +       if (reg_val & DISPC_INT_DONE_MASK) {
> > +               ctx->evt_stop = true;
> > +               wake_up_interruptible_all(&ctx->wait_queue);
> > +       }
> > +
> > +       /* dpu ifbc payload error isr */
> > +       if (reg_val & DISPC_INT_FBC_PLD_ERR_MASK) {
> > +               int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> > +               pr_err("dpu ifbc payload error\n");
> > +       }
> > +
> > +       /* dpu ifbc header error isr */
> > +       if (reg_val & DISPC_INT_FBC_HDR_ERR_MASK) {
> > +               int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> > +               pr_err("dpu ifbc header error\n");
> > +       }
> > +
> > +       int_mask |= check_mmu_isr(ctx, reg_val);
> > +
> > +       reg->dpu_int_clr = reg_val;
> > +       reg->dpu_int_en &= ~int_mask;
>
> Also, not coding style. Use readl/writel{_relaxed}. This is fragile
> because the compiler has a lot of freedom in what it can do here
> including reordering the accesses, changing the access size or
> skipping the register access altogether. I don't think the latter
> would happen in this case, but if a read has side effects such as
> causing bits to clear you'll see problems.
>
This is maybe a bad design for a long time on our platform, but it ’s
really convenient,
direct access I/O resources by structs...
So i will be try use readl/writel to replace it.

>
> > +
> > +       return reg_val;
> > +}
> > +
> > +static int dpu_wait_stop_done(struct dpu_context *ctx)
> > +{
> > +       int rc;
> > +
> > +       if (ctx->stopped)
> > +               return 0;
> > +
> > +       rc = wait_event_interruptible_timeout(ctx->wait_queue,
> ctx->evt_stop,
> > +                                              msecs_to_jiffies(500));
> > +       ctx->evt_stop = false;
> > +
> > +       ctx->stopped = true;
> > +
> > +       if (!rc) {
> > +               pr_err("dpu wait for stop done time out!\n");
> > +               return -ETIMEDOUT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int dpu_wait_update_done(struct dpu_context *ctx)
> > +{
> > +       int rc;
> > +
> > +       ctx->evt_update = false;
> > +
> > +       rc = wait_event_interruptible_timeout(ctx->wait_queue,
> ctx->evt_update,
> > +                                              msecs_to_jiffies(500));
> > +
> > +       if (!rc) {
> > +               pr_err("dpu wait for reg update done time out!\n");
> > +               return -ETIMEDOUT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static void dpu_stop(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (ctx->stopped)
> > +               return;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> > +               reg->dpu_ctrl |= BIT(1);
> > +
> > +       dpu_wait_stop_done(ctx);
> > +
> > +       pr_info("dpu stop\n");
> > +}
> > +
> > +static void dpu_run(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (!ctx->stopped)
> > +               return;
> > +
> > +       reg->dpu_ctrl |= BIT(0);
> > +
> > +       ctx->stopped = false;
> > +
> > +       pr_info("dpu run\n");
> > +}
> > +
> > +static int dpu_init(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 size;
> > +
> > +       reg->bg_color = 0;
> > +
> > +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> > +       reg->panel_size = size;
> > +       reg->blend_size = size;
> > +
> > +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> > +
> > +       reg->dpu_cfg1 = 0x004466da;
> > +       reg->dpu_cfg2 = 0;
> > +
> > +       if (ctx->stopped)
> > +               dpu_clean_all(ctx);
> > +
> > +       reg->mmu_en = 0;
> > +       reg->mmu_min_ppn1 = 0;
> > +       reg->mmu_ppn_range1 = 0xffff;
> > +       reg->mmu_min_ppn2 = 0;
> > +       reg->mmu_ppn_range2 = 0xffff;
> > +       reg->mmu_vpn_range = 0x1ffff;
> > +
> > +       reg->dpu_int_clr = 0xffff;
> > +
> > +       init_waitqueue_head(&ctx->wait_queue);
> > +
> > +       return 0;
> > +}
> > +
> > +static void dpu_fini(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       reg->dpu_int_en = 0;
> > +       reg->dpu_int_clr = 0xff;
> > +}
> > +
> > +enum {
> > +       DPU_LAYER_FORMAT_YUV422_2PLANE,
> > +       DPU_LAYER_FORMAT_YUV420_2PLANE,
> > +       DPU_LAYER_FORMAT_YUV420_3PLANE,
> > +       DPU_LAYER_FORMAT_ARGB8888,
> > +       DPU_LAYER_FORMAT_RGB565,
> > +       DPU_LAYER_FORMAT_XFBC_ARGB8888 = 8,
> > +       DPU_LAYER_FORMAT_XFBC_RGB565,
> > +       DPU_LAYER_FORMAT_MAX_TYPES,
> > +};
> > +
> > +enum {
> > +       DPU_LAYER_ROTATION_0,
> > +       DPU_LAYER_ROTATION_90,
> > +       DPU_LAYER_ROTATION_180,
> > +       DPU_LAYER_ROTATION_270,
> > +       DPU_LAYER_ROTATION_0_M,
> > +       DPU_LAYER_ROTATION_90_M,
> > +       DPU_LAYER_ROTATION_180_M,
> > +       DPU_LAYER_ROTATION_270_M,
> > +};
> > +
> > +static u32 to_dpu_rotation(u32 angle)
> > +{
> > +       u32 rot = DPU_LAYER_ROTATION_0;
> > +
> > +       switch (angle) {
> > +       case 0:
> > +       case DRM_MODE_ROTATE_0:
> > +               rot = DPU_LAYER_ROTATION_0;
> > +               break;
> > +       case DRM_MODE_ROTATE_90:
> > +               rot = DPU_LAYER_ROTATION_90;
> > +               break;
> > +       case DRM_MODE_ROTATE_180:
> > +               rot = DPU_LAYER_ROTATION_180;
> > +               break;
> > +       case DRM_MODE_ROTATE_270:
> > +               rot = DPU_LAYER_ROTATION_270;
> > +               break;
> > +       case DRM_MODE_REFLECT_Y:
> > +               rot = DPU_LAYER_ROTATION_180_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_90_M;
> > +               break;
> > +       case DRM_MODE_REFLECT_X:
> > +               rot = DPU_LAYER_ROTATION_0_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_270_M;
> > +               break;
> > +       default:
> > +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n",
> angle);
> > +               break;
> > +       }
> > +
> > +       return rot;
> > +}
> > +
> > +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> > +{
> > +       int reg_val = 0;
> > +
> > +       /* layer enable */
> > +       reg_val |= BIT(0);
> > +
> > +       switch (format) {
> > +       case DRM_FORMAT_BGRA8888:
> > +               /* BGRA8888 -> ARGB8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_RGBX8888:
> > +       case DRM_FORMAT_RGBA8888:
> > +               /* RGBA8888 -> ABGR8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ABGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ARGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_XBGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_XRGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_BGR565:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_RGB565:
> > +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> > +               break;
> > +       case DRM_FORMAT_NV12:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV21:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV16:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV61:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YUV420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YVU420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       default:
> > +               pr_err("error: invalid format %c%c%c%c\n", format,
> > +                                               format >> 8,
> > +                                               format >> 16,
> > +                                               format >> 24);
> > +               break;
> > +       }
> > +
> > +       switch (blending) {
> > +       case DRM_MODE_BLEND_PIXEL_NONE:
> > +               /* don't do blending, maybe RGBX */
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> > +       case DRM_MODE_BLEND_COVERAGE:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Normal mode*/
> > +               reg_val &= (~BIT(16));
> > +               break;
> > +       case DRM_MODE_BLEND_PREMULTI:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Pre-mult mode*/
> > +               reg_val |= BIT(16);
> > +               break;
> > +       default:
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> > +       }
> > +
> > +       rotation = to_dpu_rotation(rotation);
> > +       reg_val |= (rotation & 0x7) << 20;
> > +
> > +       return reg_val;
> > +}
> > +
> > +static void dpu_bgcolor(struct dpu_context *ctx, u32 color)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> > +               dpu_wait_stop_done(ctx);
> > +
> > +       reg->bg_color = color;
> > +
> > +       dpu_clean_all(ctx);
> > +
> > +       if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->stopped) {
> > +               reg->dpu_ctrl |= BIT(2);
> > +               dpu_wait_update_done(ctx);
> > +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> > +               reg->dpu_ctrl |= BIT(0);
> > +               ctx->stopped = false;
> > +       }
> > +}
> > +
> > +static void dpu_layer(struct dpu_context *ctx,
> > +                   struct sprd_dpu_layer *hwlayer)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       const struct drm_format_info *info;
> > +       struct layer_reg *layer;
> > +       u32 addr, size, offset;
> > +       int i;
> > +
> > +       layer = &reg->layers[hwlayer->index];
> > +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> > +
> > +       if (hwlayer->src_w && hwlayer->src_h)
> > +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) <<
> 16);
> > +       else
> > +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) <<
> 16);
> > +
> > +       for (i = 0; i < hwlayer->planes; i++) {
> > +               addr = hwlayer->addr[i];
> > +
> > +               if (addr % 16)
> > +                       pr_err("layer addr[%d] is not 16 bytes align,
> it's 0x%08x\n",
> > +                               i, addr);
> > +               layer->addr[i] = addr;
> > +       }
> > +
> > +       layer->pos = offset;
> > +       layer->size = size;
> > +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> > +       layer->alpha = hwlayer->alpha;
> > +
> > +       info = drm_format_info(hwlayer->format);
> > +       if (info->cpp[0] == 0) {
> > +               pr_err("layer[%d] bytes per pixel is invalid\n",
> hwlayer->index);
> > +               return;
> > +       }
> > +
> > +       if (hwlayer->planes == 3)
> > +               /* UV pitch is 1/2 of Y pitch*/
> > +               layer->pitch = (hwlayer->pitch[0] / info->cpp[0]) |
> > +                               (hwlayer->pitch[0] / info->cpp[0] << 15);
> > +       else
> > +               layer->pitch = hwlayer->pitch[0] / info->cpp[0];
> > +
> > +       layer->ctrl = dpu_img_ctrl(hwlayer->format, hwlayer->blending,
> > +               hwlayer->rotation);
> > +
> > +       pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
> > +                               hwlayer->dst_x, hwlayer->dst_y,
> > +                               hwlayer->dst_w, hwlayer->dst_h);
> > +       pr_debug("start_x = %d, start_y = %d, start_w = %d, start_h =
> %d\n",
> > +                               hwlayer->src_x, hwlayer->src_y,
> > +                               hwlayer->src_w, hwlayer->src_h);
> > +}
> > +
> > +static void dpu_flip(struct dpu_context *ctx,
> > +                    struct sprd_dpu_layer layers[], u8 count)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       int i;
> > +
> > +       /*
> > +        * Make sure the dpu is in stop status. DPU_R2P0 has no shadow
> > +        * registers in EDPI mode. So the config registers can only be
> > +        * updated in the rising edge of DPU_RUN bit.
> > +        */
> > +       if (ctx->if_type == SPRD_DISPC_IF_EDPI)
> > +               dpu_wait_stop_done(ctx);
> > +
> > +       /* reset the bgcolor to black */
> > +       reg->bg_color = 0;
> > +
> > +       /* disable all the layers */
> > +       dpu_clean_all(ctx);
> > +
> > +       /* start configure dpu layers */
> > +       for (i = 0; i < count; i++)
> > +               dpu_layer(ctx, &layers[i]);
> > +
> > +       /* update trigger and wait */
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> > +               if (!ctx->stopped) {
> > +                       reg->dpu_ctrl |= BIT(2);
> > +                       dpu_wait_update_done(ctx);
> > +               }
> > +
> > +               reg->dpu_int_en |= DISPC_INT_ERR_MASK;
> > +
> > +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> > +               reg->dpu_ctrl |= BIT(0);
> > +
> > +               ctx->stopped = false;
> > +       }
> > +
> > +       /*
> > +        * If the following interrupt was disabled in isr,
> > +        * re-enable it.
> > +        */
> > +       reg->dpu_int_en |= DISPC_INT_FBC_PLD_ERR_MASK |
> > +                          DISPC_INT_FBC_HDR_ERR_MASK |
> > +                          DISPC_INT_MMU_VAOR_RD_MASK |
> > +                          DISPC_INT_MMU_VAOR_WR_MASK |
> > +                          DISPC_INT_MMU_INV_RD_MASK |
> > +                          DISPC_INT_MMU_INV_WR_MASK;
> > +}
> > +
> > +static void dpu_dpi_init(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 int_mask = 0;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI) {
> > +               /* use dpi as interface */
> > +               reg->dpu_cfg0 &= ~BIT(0);
> > +
> > +               /* disable Halt function for SPRD DSI */
> > +               reg->dpi_ctrl &= ~BIT(16);
> > +
> > +               /* select te from external pad */
> > +               reg->dpi_ctrl |= BIT(10);
> > +
> > +               /* set dpi timing */
> > +               reg->dpi_h_timing = (ctx->vm.hsync_len << 0) |
> > +                                   (ctx->vm.hback_porch << 8) |
> > +                                   (ctx->vm.hfront_porch << 20);
> > +               reg->dpi_v_timing = (ctx->vm.vsync_len << 0) |
> > +                                   (ctx->vm.vback_porch << 8) |
> > +                                   (ctx->vm.vfront_porch << 20);
> > +               if (ctx->vm.vsync_len + ctx->vm.vback_porch < 32)
> > +                       pr_warn("Warning: (vsync + vbp) < 32, "
> > +                               "underflow risk!\n");
> > +
> > +               /* enable dpu update done INT */
> > +               int_mask |= DISPC_INT_UPDATE_DONE_MASK;
> > +               /* enable dpu DONE  INT */
> > +               int_mask |= DISPC_INT_DONE_MASK;
> > +               /* enable dpu dpi vsync */
> > +               int_mask |= DISPC_INT_DPI_VSYNC_MASK;
> > +               /* enable dpu TE INT */
> > +               int_mask |= DISPC_INT_TE_MASK;
> > +               /* enable underflow err INT */
> > +               int_mask |= DISPC_INT_ERR_MASK;
> > +               /* enable write back done INT */
> > +               int_mask |= DISPC_INT_WB_DONE_MASK;
> > +               /* enable write back fail INT */
> > +               int_mask |= DISPC_INT_WB_FAIL_MASK;
> > +
> > +       } else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
> > +               /* use edpi as interface */
> > +               reg->dpu_cfg0 |= BIT(0);
> > +
> > +               /* use external te */
> > +               reg->dpi_ctrl |= BIT(10);
> > +
> > +               /* enable te */
> > +               reg->dpi_ctrl |= BIT(8);
> > +
> > +               /* enable stop DONE INT */
> > +               int_mask |= DISPC_INT_DONE_MASK;
> > +               /* enable TE INT */
> > +               int_mask |= DISPC_INT_TE_MASK;
> > +       }
> > +
> > +       /* enable ifbc payload error INT */
> > +       int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
> > +       /* enable ifbc header error INT */
> > +       int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
> > +       /* enable iommu va out of range read error INT */
> > +       int_mask |= DISPC_INT_MMU_VAOR_RD_MASK;
> > +       /* enable iommu va out of range write error INT */
> > +       int_mask |= DISPC_INT_MMU_VAOR_WR_MASK;
> > +       /* enable iommu invalid read error INT */
> > +       int_mask |= DISPC_INT_MMU_INV_RD_MASK;
> > +       /* enable iommu invalid write error INT */
> > +       int_mask |= DISPC_INT_MMU_INV_WR_MASK;
> > +
> > +       reg->dpu_int_en = int_mask;
> > +}
> > +
> > +static void enable_vsync(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       reg->dpu_int_en |= DISPC_INT_DPI_VSYNC_MASK;
> > +}
> > +
> > +static void disable_vsync(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       reg->dpu_int_en &= ~DISPC_INT_DPI_VSYNC_MASK;
> > +}
> > +
> > +static const u32 primary_fmts[] = {
> > +       DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
> > +       DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
> > +       DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
> > +       DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
> > +       DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
> > +       DRM_FORMAT_NV12, DRM_FORMAT_NV21,
> > +       DRM_FORMAT_NV16, DRM_FORMAT_NV61,
> > +       DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
> > +};
> > +
> > +static int dpu_capability(struct dpu_context *ctx,
> > +                       struct dpu_capability *cap)
> > +{
> > +       if (!cap)
> > +               return -EINVAL;
> > +
> > +       cap->max_layers = 6;
> > +       cap->fmts_ptr = primary_fmts;
> > +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> > +
> > +       return 0;
> > +}
> > +
> > +const struct dpu_core_ops sharkl3_dpu_core_ops = {
> > +       .init = dpu_init,
> > +       .fini = dpu_fini,
> > +       .run = dpu_run,
> > +       .stop = dpu_stop,
> > +       .isr = dpu_isr,
> > +       .ifconfig = dpu_dpi_init,
> > +       .capability = dpu_capability,
> > +       .flip = dpu_flip,
> > +       .bg_color = dpu_bgcolor,
> > +       .enable_vsync = enable_vsync,
> > +       .disable_vsync = disable_vsync,
> > +};
> > diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c
> b/drivers/gpu/drm/sprd/sprd_dpu.c
> > new file mode 100644
> > index 0000000..f122b0e
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_dpu.c
> > @@ -0,0 +1,586 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (C) 2020 Unisoc Inc.
> > + */
> > +
> > +#include <linux/component.h>
> > +#include <linux/dma-buf.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_device.h>
> > +#include <linux/of_irq.h>
> > +
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_fb_cma_helper.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +#include <drm/drm_gem_framebuffer_helper.h>
> > +#include <drm/drm_plane_helper.h>
> > +
> > +#include "sprd_drm.h"
> > +#include "sprd_dpu.h"
> > +
> > +struct sprd_plane {
> > +       struct drm_plane plane;
> > +       u32 index;
> > +};
> > +
> > +static int sprd_dpu_init(struct sprd_dpu *dpu);
> > +static int sprd_dpu_fini(struct sprd_dpu *dpu);
> > +
> > +static inline struct sprd_plane *to_sprd_plane(struct drm_plane *plane)
> > +{
> > +       return container_of(plane, struct sprd_plane, plane);
> > +}
> > +
> > +static int sprd_plane_atomic_check(struct drm_plane *plane,
> > +                                 struct drm_plane_state *state)
> > +{
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       return 0;
> > +}
> > +
> > +static void sprd_plane_atomic_update(struct drm_plane *plane,
> > +                                   struct drm_plane_state *old_state)
> > +{
> > +       struct drm_plane_state *state = plane->state;
> > +       struct drm_framebuffer *fb = plane->state->fb;
> > +       struct drm_gem_cma_object *cma_obj;
> > +       struct sprd_plane *p = to_sprd_plane(plane);
> > +       struct sprd_dpu *dpu = crtc_to_dpu(plane->state->crtc);
> > +       struct sprd_dpu_layer *layer = &dpu->layers[p->index];
> > +       int i;
> > +
> > +       if (plane->state->crtc->state->active_changed) {
> > +               DRM_DEBUG("resume or suspend, no need to update
> plane\n");
> > +               return;
> > +       }
> > +
> > +       layer->index = p->index;
> > +       layer->src_x = state->src_x >> 16;
> > +       layer->src_y = state->src_y >> 16;
> > +       layer->src_w = state->src_w >> 16;
> > +       layer->src_h = state->src_h >> 16;
> > +       layer->dst_x = state->crtc_x;
> > +       layer->dst_y = state->crtc_y;
> > +       layer->dst_w = state->crtc_w;
> > +       layer->dst_h = state->crtc_h;
> > +       layer->alpha = state->alpha;
> > +       layer->blending = state->pixel_blend_mode;
> > +       layer->rotation = state->rotation;
> > +       layer->planes = fb->format->num_planes;
> > +       layer->format = fb->format->format;
> > +
> > +       DRM_DEBUG("%s() alpha = %u, blending = %u, rotation = %u\n",
> > +                 __func__, layer->alpha, layer->blending,
> layer->rotation);
> > +
> > +       for (i = 0; i < layer->planes; i++) {
> > +               cma_obj = drm_fb_cma_get_gem_obj(fb, i);
> > +               layer->addr[i] = cma_obj->paddr + fb->offsets[i];
> > +               layer->pitch[i] = fb->pitches[i];
> > +       }
> > +
> > +       dpu->pending_planes++;
> > +}
> > +
> > +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> > +                                    struct drm_plane_state *old_state)
> > +{
> > +       struct sprd_plane *p = to_sprd_plane(plane);
> > +
> > +       /*
> > +        * NOTE:
> > +        * The dpu->core->flip() will disable all the planes each time.
> > +        * So there is no need to impliment the atomic_disable()
> function.
> > +        * But this function can not be removed, because it will change
> > +        * to call atomic_update() callback instead. Which will cause
> > +        * kernel panic in sprd_plane_atomic_update().
> > +        *
> > +        * We do nothing here but just print a debug log.
> > +        */
> > +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
> > +}
> > +
> > +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> > +{
> > +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> > +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> > +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> > +
> > +       /* create rotation property */
> > +       drm_plane_create_rotation_property(&p->plane,
> > +                                          DRM_MODE_ROTATE_0,
> > +                                          DRM_MODE_ROTATE_MASK |
> > +                                          DRM_MODE_REFLECT_MASK);
> > +
> > +       /* create alpha property */
> > +       drm_plane_create_alpha_property(&p->plane);
> > +
> > +       /* create blend mode property */
> > +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> > +
> > +       /* create zpos property */
> > +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct drm_plane_helper_funcs sprd_plane_helper_funcs = {
> > +       .atomic_check = sprd_plane_atomic_check,
> > +       .atomic_update = sprd_plane_atomic_update,
> > +       .atomic_disable = sprd_plane_atomic_disable,
> > +};
> > +
> > +static const struct drm_plane_funcs sprd_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 struct drm_plane *sprd_plane_init(struct drm_device *drm,
> > +                                       struct sprd_dpu *dpu)
> > +{
> > +       struct drm_plane *primary = NULL;
> > +       struct sprd_plane *p = NULL;
> > +       struct dpu_capability cap = {};
> > +       int err, i;
> > +
> > +       if (dpu->core && dpu->core->capability)
> > +               dpu->core->capability(&dpu->ctx, &cap);
> > +
> > +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> > +                                 sizeof(struct sprd_dpu_layer),
> GFP_KERNEL);
> > +       if (!dpu->layers)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       for (i = 0; i < cap.max_layers; i++) {
> > +
> > +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> > +               if (!p)
> > +                       return ERR_PTR(-ENOMEM);
> > +
> > +               err = drm_universal_plane_init(drm, &p->plane, 1,
> > +                                              &sprd_plane_funcs,
> cap.fmts_ptr,
> > +                                              cap.fmts_cnt, NULL,
> > +                                              DRM_PLANE_TYPE_PRIMARY,
> NULL);
> > +               if (err) {
> > +                       DRM_ERROR("fail to init primary plane\n");
> > +                       return ERR_PTR(err);
> > +               }
> > +
> > +               drm_plane_helper_add(&p->plane,
> &sprd_plane_helper_funcs);
> > +
> > +               sprd_plane_create_properties(p, i);
> > +
> > +               p->index = i;
> > +               if (i == 0)
> > +                       primary = &p->plane;
> > +       }
> > +
> > +       if (p)
> > +               DRM_INFO("dpu plane init ok\n");
> > +
> > +       return primary;
> > +}
> > +
> > +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> > +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> > +       else
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> > +}
> > +
> > +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> > +                                       const struct drm_display_mode
> *mode)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__,
> DRM_MODE_ARG(mode));
> > +
> > +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> > +               dpu->mode = (struct drm_display_mode *)mode;
> > +
> > +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> > +               dpu->mode = (struct drm_display_mode *)mode;
> > +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
> > +       }
> > +
> > +       return MODE_OK;
> > +}
> > +
> > +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> > +                                  struct drm_crtc_state *old_state)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> > +       sprd_dpu_init(dpu);
> > +
> > +       enable_irq(dpu->ctx.irq);
> > +
> > +       drm_crtc_vblank_on(crtc);
> > +}
> > +
> > +static void sprd_crtc_atomic_disable(struct drm_crtc *crtc,
> > +                                   struct drm_crtc_state *old_state)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +       struct drm_device *drm = dpu->crtc.dev;
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> > +       drm_crtc_vblank_off(crtc);
> > +
> > +       disable_irq(dpu->ctx.irq);
> > +
> > +       sprd_dpu_fini(dpu);
> > +
> > +       spin_lock_irq(&drm->event_lock);
> > +       if (crtc->state->event) {
> > +               drm_crtc_send_vblank_event(crtc, crtc->state->event);
> > +               crtc->state->event = NULL;
> > +       }
> > +       spin_unlock_irq(&drm->event_lock);
> > +}
> > +
> > +static int sprd_crtc_atomic_check(struct drm_crtc *crtc,
> > +                                struct drm_crtc_state *state)
> > +{
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       return 0;
> > +}
> > +
> > +static void sprd_crtc_atomic_begin(struct drm_crtc *crtc,
> > +                                 struct drm_crtc_state *old_state)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       memset(dpu->layers, 0, sizeof(*dpu->layers) *
> dpu->pending_planes);
> > +
> > +       dpu->pending_planes = 0;
> > +}
> > +
> > +static void sprd_crtc_atomic_flush(struct drm_crtc *crtc,
> > +                                 struct drm_crtc_state *old_state)
> > +
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +       struct drm_device *drm = dpu->crtc.dev;
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       if (dpu->core && dpu->core->flip && dpu->pending_planes)
> > +               dpu->core->flip(&dpu->ctx, dpu->layers,
> dpu->pending_planes);
> > +
> > +       spin_lock_irq(&drm->event_lock);
> > +       if (crtc->state->event) {
> > +               drm_crtc_send_vblank_event(crtc, crtc->state->event);
> > +               crtc->state->event = NULL;
> > +       }
> > +       spin_unlock_irq(&drm->event_lock);
> > +}
> > +
> > +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       if (dpu->core && dpu->core->enable_vsync)
> > +               dpu->core->enable_vsync(&dpu->ctx);
> > +
> > +       return 0;
> > +}
> > +
> > +static void sprd_crtc_disable_vblank(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> > +       if (dpu->core && dpu->core->disable_vsync)
> > +               dpu->core->disable_vsync(&dpu->ctx);
> > +}
> > +
> > +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +       struct drm_device *drm = dpu->crtc.dev;
> > +       struct drm_property *prop;
> > +       struct drm_property_blob *blob;
> > +       size_t blob_size;
> > +
> > +       blob_size = strlen(dpu->ctx.version) + 1;
> > +
> > +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> > +                       dpu->ctx.version);
> > +       if (IS_ERR(blob)) {
> > +               DRM_ERROR("drm_property_create_blob dpu version
> failed\n");
> > +               return PTR_ERR(blob);
> > +       }
> > +
> > +       /* create dpu version property */
> > +       prop = drm_property_create(drm,
> > +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> > +               "dpu version", 0);
> > +       if (!prop) {
> > +               DRM_ERROR("drm_property_create dpu version failed\n");
> > +               return -ENOMEM;
> > +       }
> > +       drm_object_attach_property(&crtc->base, prop, blob->base.id);
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = {
> > +       .mode_set_nofb  = sprd_crtc_mode_set_nofb,
> > +       .mode_valid     = sprd_crtc_mode_valid,
> > +       .atomic_check   = sprd_crtc_atomic_check,
> > +       .atomic_begin   = sprd_crtc_atomic_begin,
> > +       .atomic_flush   = sprd_crtc_atomic_flush,
> > +       .atomic_enable  = sprd_crtc_atomic_enable,
> > +       .atomic_disable = sprd_crtc_atomic_disable,
> > +};
> > +
> > +static const struct drm_crtc_funcs sprd_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  = sprd_crtc_enable_vblank,
> > +       .disable_vblank = sprd_crtc_disable_vblank,
> > +};
> > +
> > +static int sprd_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
> > +                        struct drm_plane *primary)
> > +{
> > +       struct device_node *port;
> > +       int err;
> > +
> > +       /*
> > +        * set crtc port so that drm_of_find_possible_crtcs call works
> > +        */
> > +       port = of_parse_phandle(drm->dev->of_node, "ports", 0);
> > +       if (!port) {
> > +               DRM_ERROR("find 'ports' phandle of %s failed\n",
> > +                         drm->dev->of_node->full_name);
> > +               return -EINVAL;
> > +       }
> > +       of_node_put(port);
> > +       crtc->port = port;
> > +
> > +       err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
> > +                                       &sprd_crtc_funcs, NULL);
> > +       if (err) {
> > +               DRM_ERROR("failed to init crtc.\n");
> > +               return err;
> > +       }
> > +
> > +       drm_mode_crtc_set_gamma_size(crtc, 256);
> > +
> > +       drm_crtc_helper_add(crtc, &sprd_crtc_helper_funcs);
> > +
> > +       sprd_crtc_create_properties(crtc);
> > +
> > +       DRM_INFO("%s() ok\n", __func__);
> > +       return 0;
> > +}
> > +
> > +static int sprd_dpu_init(struct sprd_dpu *dpu)
> > +{
> > +       struct dpu_context *ctx = &dpu->ctx;
> > +
> > +       if (dpu->core && dpu->core->init)
> > +               dpu->core->init(ctx);
> > +       if (dpu->core && dpu->core->ifconfig)
> > +               dpu->core->ifconfig(ctx);
> > +
> > +       return 0;
> > +}
> > +
> > +static int sprd_dpu_fini(struct sprd_dpu *dpu)
> > +{
> > +       struct dpu_context *ctx = &dpu->ctx;
> > +
> > +       if (dpu->core && dpu->core->fini)
> > +               dpu->core->fini(ctx);
> > +
> > +       return 0;
> > +}
> > +
> > +static irqreturn_t sprd_dpu_isr(int irq, void *data)
> > +{
> > +       struct sprd_dpu *dpu = data;
> > +       struct dpu_context *ctx = &dpu->ctx;
> > +       u32 int_mask = 0;
> > +
> > +       if (dpu->core && dpu->core->isr)
> > +               int_mask = dpu->core->isr(ctx);
> > +
> > +       if (int_mask & DISPC_INT_ERR_MASK)
> > +               DRM_WARN("Warning: dpu underflow!\n");
> > +
> > +       if ((int_mask & DISPC_INT_DPI_VSYNC_MASK))
> > +               drm_crtc_handle_vblank(&dpu->crtc);
> > +
> > +       return IRQ_HANDLED;
> > +}
> > +
> > +static int sprd_dpu_irq_request(struct sprd_dpu *dpu)
> > +{
> > +       int err;
> > +       int irq_num;
> > +
> > +       irq_num = irq_of_parse_and_map(dpu->dev.of_node, 0);
>
> Use platform_get_irq instead.
>
> > +       if (!irq_num) {
> > +               DRM_ERROR("error: dpu parse irq num failed\n");
> > +               return -EINVAL;
> > +       }
> > +       DRM_INFO("dpu irq_num = %d\n", irq_num);
> > +
> > +       irq_set_status_flags(irq_num, IRQ_NOAUTOEN);
>
> I think you shouldn't need this. Make sure you've disabled interrupts
> in the h/w first or that the handler can handle it if you haven't.
>
Sorry, our h/w can't disabled interrupt after power on, so we need to
disabled irq handler when irq request.

>
> > +       err = devm_request_irq(&dpu->dev, irq_num, sprd_dpu_isr,
> > +                                       0, "DISPC", dpu);
> > +       if (err) {
> > +               DRM_ERROR("error: dpu request irq failed\n");
> > +               return -EINVAL;
> > +       }
> > +       dpu->ctx.irq = irq_num;
> > +
> > +       return 0;
> > +}
> > +
> > +static int sprd_dpu_bind(struct device *dev, struct device *master,
> void *data)
> > +{
> > +       struct drm_device *drm = data;
> > +       struct sprd_dpu *dpu = dev_get_drvdata(dev);
> > +       struct drm_plane *plane;
> > +       int err;
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> > +       plane = sprd_plane_init(drm, dpu);
> > +       if (IS_ERR_OR_NULL(plane)) {
> > +               err = PTR_ERR(plane);
> > +               return err;
> > +       }
> > +
> > +       err = sprd_crtc_init(drm, &dpu->crtc, plane);
> > +       if (err)
> > +               return err;
> > +
> > +       sprd_dpu_irq_request(dpu);
> > +
> > +       return 0;
> > +}
> > +
> > +static void sprd_dpu_unbind(struct device *dev, struct device *master,
> > +       void *data)
> > +{
> > +       struct sprd_dpu *dpu = dev_get_drvdata(dev);
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> > +       drm_crtc_cleanup(&dpu->crtc);
> > +}
> > +
> > +static const struct component_ops dpu_component_ops = {
> > +       .bind = sprd_dpu_bind,
> > +       .unbind = sprd_dpu_unbind,
> > +};
> > +
> > +static int sprd_dpu_context_init(struct sprd_dpu *dpu,
> > +                               struct device_node *np)
> > +{
> > +       struct resource r;
> > +       struct dpu_context *ctx = &dpu->ctx;
> > +
> > +       if (of_address_to_resource(np, 0, &r)) {
> > +               DRM_ERROR("parse dt base address failed\n");
> > +               return -ENODEV;
> > +       }
> > +       ctx->base = (unsigned long)ioremap(r.start,
> > +                                       resource_size(&r));
> > +       if (ctx->base == 0) {
> > +               DRM_ERROR("ioremap base address failed\n");
> > +               return -EFAULT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static const struct sprd_dpu_ops sharkl3_dpu = {
> > +       .core = &sharkl3_dpu_core_ops,
> > +};
> > +
> > +static const struct of_device_id dpu_match_table[] = {
> > +       { .compatible = "sprd,sharkl3-dpu",
> > +         .data = &sharkl3_dpu },
> > +       { /* sentinel */ },
> > +};
> > +
> > +static int sprd_dpu_probe(struct platform_device *pdev)
> > +{
> > +       struct device_node *np = pdev->dev.of_node;
> > +       const struct of_device_id *of_id =
> > +               of_match_node(dpu_match_table, np);
>
> Don't need this.
>
> > +       const struct sprd_dpu_ops *pdata;
> > +       struct sprd_dpu *dpu;
> > +       int ret;
> > +
> > +       dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
> > +       if (!dpu)
> > +               return -ENOMEM;
> > +
> > +       pdata = of_device_get_match_data(&pdev->dev);
> > +       if (pdata) {
> > +               dpu->core = pdata->core;
> > +               dpu->ctx.version = "dpu-r2p0";
> > +       } else {
> > +               DRM_ERROR("Can't get %s ops data\n", of_id->name);
> > +               return -EINVAL;
> > +       }
> > +
> > +       ret = sprd_dpu_context_init(dpu, np);
> > +       if (ret)
> > +               return ret;
> > +
> > +       platform_set_drvdata(pdev, dpu);
> > +
> > +       return component_add(&pdev->dev, &dpu_component_ops);
> > +}
> > +
> > +static int sprd_dpu_remove(struct platform_device *pdev)
> > +{
> > +       component_del(&pdev->dev, &dpu_component_ops);
> > +       return 0;
> > +}
> > +
> > +struct platform_driver sprd_dpu_driver = {
> > +       .probe = sprd_dpu_probe,
> > +       .remove = sprd_dpu_remove,
> > +       .driver = {
> > +               .name = "sprd-dpu-drv",
> > +               .of_match_table = dpu_match_table,
> > +       },
> > +};
> > +
> > +MODULE_AUTHOR("Leon He <leon.he@unisoc.com>");
> > +MODULE_AUTHOR("Kevin Tang <kevin.tang@unisoc.com>");
> > +MODULE_DESCRIPTION("Unisoc Display Controller Driver");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/drivers/gpu/drm/sprd/sprd_dpu.h
> b/drivers/gpu/drm/sprd/sprd_dpu.h
> > new file mode 100644
> > index 0000000..3e7f91f
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/sprd_dpu.h
> > @@ -0,0 +1,127 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (C) 2020 Unisoc Inc.
> > + */
> > +
> > +#ifndef __SPRD_DPU_H__
> > +#define __SPRD_DPU_H__
> > +
> > +#include <linux/bug.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/kernel.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/string.h>
> > +#include <video/videomode.h>
> > +
> > +#include <drm/drm_crtc.h>
> > +#include <drm/drm_fourcc.h>
> > +#include <drm/drm_print.h>
> > +#include <drm/drm_vblank.h>
> > +#include <uapi/drm/drm_mode.h>
> > +#include "disp_lib.h"
> > +
> > +#define DISPC_INT_DONE_MASK            BIT(0)
> > +#define DISPC_INT_TE_MASK              BIT(1)
> > +#define DISPC_INT_ERR_MASK             BIT(2)
> > +#define DISPC_INT_EDPI_TE_MASK         BIT(3)
> > +#define DISPC_INT_UPDATE_DONE_MASK     BIT(4)
> > +#define DISPC_INT_DPI_VSYNC_MASK       BIT(5)
> > +#define DISPC_INT_WB_DONE_MASK         BIT(6)
> > +#define DISPC_INT_WB_FAIL_MASK         BIT(7)
> > +
> > +/* NOTE: this mask is not a realy dpu interrupt mask */
> > +#define DISPC_INT_FENCE_SIGNAL_REQUEST BIT(31)
> > +
> > +enum {
> > +       SPRD_DISPC_IF_DBI = 0,
> > +       SPRD_DISPC_IF_DPI,
> > +       SPRD_DISPC_IF_EDPI,
> > +       SPRD_DISPC_IF_LIMIT
> > +};
> > +
> > +enum {
> > +       SPRD_IMG_DATA_ENDIAN_B0B1B2B3 = 0,
> > +       SPRD_IMG_DATA_ENDIAN_B3B2B1B0,
> > +       SPRD_IMG_DATA_ENDIAN_B2B3B0B1,
> > +       SPRD_IMG_DATA_ENDIAN_B1B0B3B2,
> > +       SPRD_IMG_DATA_ENDIAN_LIMIT
> > +};
> > +
> > +struct sprd_dpu_layer {
> > +       u8 index;
> > +       u8 planes;
> > +       u32 addr[4];
> > +       u32 pitch[4];
> > +       s16 src_x;
> > +       s16 src_y;
> > +       s16 src_w;
> > +       s16 src_h;
> > +       s16 dst_x;
> > +       s16 dst_y;
> > +       u16 dst_w;
> > +       u16 dst_h;
> > +       u32 format;
> > +       u32 alpha;
> > +       u32 blending;
> > +       u32 rotation;
> > +};
> > +
> > +struct dpu_capability {
> > +       u32 max_layers;
> > +       const u32 *fmts_ptr;
> > +       u32 fmts_cnt;
> > +};
> > +
> > +struct dpu_context;
> > +
> > +struct dpu_core_ops {
> > +       int (*init)(struct dpu_context *ctx);
> > +       void (*fini)(struct dpu_context *ctx);
> > +       void (*run)(struct dpu_context *ctx);
> > +       void (*stop)(struct dpu_context *ctx);
> > +       void (*disable_vsync)(struct dpu_context *ctx);
> > +       void (*enable_vsync)(struct dpu_context *ctx);
> > +       u32 (*isr)(struct dpu_context *ctx);
> > +       void (*ifconfig)(struct dpu_context *ctx);
> > +       void (*flip)(struct dpu_context *ctx,
> > +                    struct sprd_dpu_layer layers[], u8 count);
> > +       int (*capability)(struct dpu_context *ctx,
> > +                       struct dpu_capability *cap);
> > +       void (*bg_color)(struct dpu_context *ctx, u32 color);
> > +};
> > +
> > +struct sprd_dpu_ops {
> > +       const struct dpu_core_ops *core;
> > +};
> > +
> > +struct dpu_context {
> > +       unsigned long base;
> > +       const char *version;
> > +       int irq;
> > +       u8 if_type;
> > +       struct videomode vm;
> > +       bool stopped;
> > +       wait_queue_head_t wait_queue;
> > +       bool evt_update;
> > +       bool evt_stop;
> > +};
> > +
> > +struct sprd_dpu {
> > +       struct device dev;
> > +       struct drm_crtc crtc;
> > +       struct dpu_context ctx;
> > +       const struct dpu_core_ops *core;
> > +       struct drm_display_mode *mode;
> > +       struct sprd_dpu_layer *layers;
> > +       u8 pending_planes;
> > +};
> > +
> > +static inline struct sprd_dpu *crtc_to_dpu(struct drm_crtc *crtc)
> > +{
> > +       return crtc ? container_of(crtc, struct sprd_dpu, crtc) : NULL;
> > +}
> > +
> > +extern const struct dpu_core_ops sharkl3_dpu_core_ops;
> > +
> > +#endif
> > diff --git a/drivers/gpu/drm/sprd/sprd_drm.c
> b/drivers/gpu/drm/sprd/sprd_drm.c
> > index 4706185..200020f 100644
> > --- a/drivers/gpu/drm/sprd/sprd_drm.c
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.c
> > @@ -200,6 +200,7 @@ static struct platform_driver sprd_drm_driver = {
> >
> >  static struct platform_driver *sprd_drm_drivers[]  = {
> >         &sprd_drm_driver,
> > +       &sprd_dpu_driver,
> >  };
> >
> >  static int __init sprd_drm_init(void)
> > diff --git a/drivers/gpu/drm/sprd/sprd_drm.h
> b/drivers/gpu/drm/sprd/sprd_drm.h
> > index edf0881..3c32f3a 100644
> > --- a/drivers/gpu/drm/sprd/sprd_drm.h
> > +++ b/drivers/gpu/drm/sprd/sprd_drm.h
> > @@ -13,4 +13,6 @@ struct sprd_drm {
> >         struct drm_device *drm;
> >  };
> >
> > +extern struct platform_driver sprd_dpu_driver;
> > +
> >  #endif /* _SPRD_DRM_H_ */
> > --
> > 2.7.4
> >
>
Kevin Tang March 5, 2020, 1:15 p.m. UTC | #5
On Tue, Mar 3, 2020 at 2:29 AM Emil Velikov <emil.l.velikov@gmail.com>
wrote:

> Hi Kevin,
>
> There's a few small suggestions, although overall the driver looks a lot
> better.
>
> On Thu, 27 Feb 2020 at 08:14, Kevin Tang <kevin3.tang@gmail.com> wrote:
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> > @@ -0,0 +1,7 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ifdef CONFIG_ARM64
> > +KBUILD_CFLAGS += -mstrict-align
>
>
> There are many other drivers that do not use readl/writel for register
> access,
> yet none has this workaround... Even those that they are exclusively ARM64.
>
> Have you tried that it's not a buggy version of GCC? At the very least, I'd
> encourage you to add a brief comment about the problem + setup.
>
> ... In general I think one should follow the suggestions from Rob Herring.
>
>
> > +static void dpu_dump(struct dpu_context *ctx)
> > +{
> > +       u32 *reg = (u32 *)ctx->base;
> > +       int i;
> > +
> > +       pr_info("      0          4          8          C\n");
> > +       for (i = 0; i < 256; i += 4) {
> > +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> > +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i +
> 3]);
>
> Using some of the helpers from drm_print.h would be better than pr_*.
> This applies for the rest of the patch.
>
>
> > +static void dpu_clean_all(struct dpu_context *ctx)
> > +{
> > +       int i;
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       for (i = 0; i < 8; i++)
>
> This "< 8" seem pretty magical. How about "< ARRAY_SIZE(reg->layers)"
> Same logic applies through the rest of the patch.
>
>
> > +static int dpu_wait_stop_done(struct dpu_context *ctx)
> > +{
> > +       int rc;
> > +
> > +       if (ctx->stopped)
> > +               return 0;
> > +
> The stopped handling does look suspicious. Admittedly I did not look too
> closely
> at the dpu_flip code, which seems to require it.
>
> Let's add a small comment in the struct dpu_context::stopped declaration,
> why it
> is needed, if it truely is.
>
Dpu have running and stopped states, "stopped " declaration is for dpu stop
done isr signal.
if we need to stop the dpu or dpu_flip, we must wait for stop done isr
signal, for waiting last h/w composer done.

Not only suspend/resume and dpu_flip, we have other scenes that need this
flag, eg: background color, PQ enhance...
All this need "stopped" flag to sync.

>
> > +       rc = wait_event_interruptible_timeout(ctx->wait_queue,
> ctx->evt_stop,
> > +                                              msecs_to_jiffies(500));
> > +       ctx->evt_stop = false;
> > +
> > +       ctx->stopped = true;
> > +
> > +       if (!rc) {
> > +               pr_err("dpu wait for stop done time out!\n");
> > +               return -ETIMEDOUT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
>
> > +static void dpu_stop(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (ctx->stopped)
> > +               return;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> > +               reg->dpu_ctrl |= BIT(1);
> > +
> > +       dpu_wait_stop_done(ctx);
> > +
> > +       pr_info("dpu stop\n");
>
> This and the dpu_run pr_info() messages can be removed.
>
>
> > +}
> > +
> > +static void dpu_run(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (!ctx->stopped)
> > +               return;
> > +
> > +       reg->dpu_ctrl |= BIT(0);
> > +
> > +       ctx->stopped = false;
> > +
> > +       pr_info("dpu run\n");
> > +}
> > +
> > +static int dpu_init(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 size;
> > +
> > +       reg->bg_color = 0;
> > +
> > +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> > +       reg->panel_size = size;
> > +       reg->blend_size = size;
> > +
> > +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> > +
> > +       reg->dpu_cfg1 = 0x004466da;
> > +       reg->dpu_cfg2 = 0;
> > +
> > +       if (ctx->stopped)
> > +               dpu_clean_all(ctx);
> > +
> > +       reg->mmu_en = 0;
> > +       reg->mmu_min_ppn1 = 0;
> > +       reg->mmu_ppn_range1 = 0xffff;
> > +       reg->mmu_min_ppn2 = 0;
> > +       reg->mmu_ppn_range2 = 0xffff;
> > +       reg->mmu_vpn_range = 0x1ffff;
> > +
> > +       reg->dpu_int_clr = 0xffff;
> > +
> > +       init_waitqueue_head(&ctx->wait_queue);
> > +
> > +       return 0;
>
> Function always returns 0. Let's make it static void dpu_init()
>
>
>
> > +static u32 to_dpu_rotation(u32 angle)
> > +{
> > +       u32 rot = DPU_LAYER_ROTATION_0;
> > +
> > +       switch (angle) {
> > +       case 0:
> > +       case DRM_MODE_ROTATE_0:
> > +               rot = DPU_LAYER_ROTATION_0;
> > +               break;
> > +       case DRM_MODE_ROTATE_90:
> > +               rot = DPU_LAYER_ROTATION_90;
> > +               break;
> > +       case DRM_MODE_ROTATE_180:
> > +               rot = DPU_LAYER_ROTATION_180;
> > +               break;
> > +       case DRM_MODE_ROTATE_270:
> > +               rot = DPU_LAYER_ROTATION_270;
> > +               break;
> > +       case DRM_MODE_REFLECT_Y:
> > +               rot = DPU_LAYER_ROTATION_180_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_90_M;
> > +               break;
> > +       case DRM_MODE_REFLECT_X:
> > +               rot = DPU_LAYER_ROTATION_0_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_270_M;
> > +               break;
> > +       default:
> > +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n",
> angle);
> > +               break;
>
> Have you seen a case where the 0 or default case are reached? AFAICT they
> will
> never trigger. So one might as well use:

    switch (angle) {
>     case DRM_MODE_FOO:
>         return DPU_LAYER_ROTATION_FOO;
>     ...
>     case DRM_MODE_BAR:
>         return DPU_LAYER_ROTATION_BAR;
>     }
>
> Yeah, the 0 maybe unused code, i will remove it.
But i think default is need, because userspace could give an incorrect
value .
So we need to setup a default value and doing error check.

>
> > +       }
> > +
> > +       return rot;
> > +}
> > +
> > +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> > +{
> > +       int reg_val = 0;
> > +
> > +       /* layer enable */
> > +       reg_val |= BIT(0);
> > +
> > +       switch (format) {
> > +       case DRM_FORMAT_BGRA8888:
> > +               /* BGRA8888 -> ARGB8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_RGBX8888:
> > +       case DRM_FORMAT_RGBA8888:
> > +               /* RGBA8888 -> ABGR8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ABGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ARGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_XBGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_XRGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_BGR565:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_RGB565:
> > +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> > +               break;
> > +       case DRM_FORMAT_NV12:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV21:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV16:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV61:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YUV420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YVU420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       default:
> > +               pr_err("error: invalid format %c%c%c%c\n", format,
> > +                                               format >> 8,
> > +                                               format >> 16,
> > +                                               format >> 24);
> > +               break;
> The default case here should be unreachable. Either it is or the upper
> layer (or
> earlier code) should ensure that.
>
There will be some differences in the formats supported by different chips,
but userspace will only have one set of code.
So it is necessary to check whether the parameters passed by the user layer
are wrong. I think it is necessary

>
> > +       }
> > +
> > +       switch (blending) {
> > +       case DRM_MODE_BLEND_PIXEL_NONE:
> > +               /* don't do blending, maybe RGBX */
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> > +       case DRM_MODE_BLEND_COVERAGE:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Normal mode*/
> > +               reg_val &= (~BIT(16));
> > +               break;
> > +       case DRM_MODE_BLEND_PREMULTI:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Pre-mult mode*/
> > +               reg_val |= BIT(16);
> > +               break;
> > +       default:
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> Ditto
>
> > +       }
> > +
> > +       rotation = to_dpu_rotation(rotation);
> > +       reg_val |= (rotation & 0x7) << 20;
> > +
> > +       return reg_val;
> > +}
> > +
>
> > +static void dpu_layer(struct dpu_context *ctx,
> > +                   struct sprd_dpu_layer *hwlayer)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       const struct drm_format_info *info;
> > +       struct layer_reg *layer;
> > +       u32 addr, size, offset;
> > +       int i;
> > +
> > +       layer = &reg->layers[hwlayer->index];
> > +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> > +
> > +       if (hwlayer->src_w && hwlayer->src_h)
> > +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) <<
> 16);
> > +       else
> > +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) <<
> 16);
> > +
> > +       for (i = 0; i < hwlayer->planes; i++) {
> > +               addr = hwlayer->addr[i];
> > +
> > +               if (addr % 16)
> > +                       pr_err("layer addr[%d] is not 16 bytes align,
> it's 0x%08x\n",
> > +                               i, addr);
> > +               layer->addr[i] = addr;
> > +       }
> > +
> > +       layer->pos = offset;
> > +       layer->size = size;
> > +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> > +       layer->alpha = hwlayer->alpha;
> > +
> > +       info = drm_format_info(hwlayer->format);
> > +       if (info->cpp[0] == 0) {
>
> Ditto
>
> > +               pr_err("layer[%d] bytes per pixel is invalid\n",
> hwlayer->index);
> > +               return;
> > +       }
> > +
>
>
>
>
> > +static int dpu_capability(struct dpu_context *ctx,
> > +                       struct dpu_capability *cap)
> > +{
> > +       if (!cap)
> > +               return -EINVAL;
> > +
> Ensure the caller always passes cap != NULL and drop the function return
> type?


> > +       cap->max_layers = 6;
> > +       cap->fmts_ptr = primary_fmts;
> > +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> > +
> > +       return 0;
> > +}
>
>
> > +static int sprd_plane_atomic_check(struct drm_plane *plane,
> > +                                 struct drm_plane_state *state)
> > +{
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
>
> Would be nice to hear from the atomic experts, how a no-op atomic_check
> goes
> with the overall atomic semantics.
>
>
> > +       return 0;
> > +}
> > +
>
>
> > +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> > +                                    struct drm_plane_state *old_state)
> > +{
> > +       struct sprd_plane *p = to_sprd_plane(plane);
> > +
> > +       /*
> > +        * NOTE:
> > +        * The dpu->core->flip() will disable all the planes each time.
> > +        * So there is no need to impliment the atomic_disable()
> function.
> > +        * But this function can not be removed, because it will change
> > +        * to call atomic_update() callback instead. Which will cause
> > +        * kernel panic in sprd_plane_atomic_update().
> > +        *
> > +        * We do nothing here but just print a debug log.
> > +        */
> > +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
>
> Similar to the check - would be nice to see a confirmation, that this isn't
> abusing atomics in some way.
>
>
> > +}
> > +
> > +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> > +{
> > +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> > +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> > +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> > +
> > +       /* create rotation property */
> > +       drm_plane_create_rotation_property(&p->plane,
> > +                                          DRM_MODE_ROTATE_0,
> > +                                          DRM_MODE_ROTATE_MASK |
> > +                                          DRM_MODE_REFLECT_MASK);
> > +
> > +       /* create alpha property */
> > +       drm_plane_create_alpha_property(&p->plane);
> > +
> > +       /* create blend mode property */
> > +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> > +
> > +       /* create zpos property */
> > +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> > +
> Either check if creating the properties fail (and propagate the error) or
> drop
> the function return type. As-is it's in the middle making it fairly
> misleading.
>
Ok, i will drop the func return type.

>
> > +       return 0;
> > +}
> > +
>
>
> > +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> > +                                       struct sprd_dpu *dpu)
> > +{
> > +       struct drm_plane *primary = NULL;
> > +       struct sprd_plane *p = NULL;
> > +       struct dpu_capability cap = {};
> > +       int err, i;
> > +
> > +       if (dpu->core && dpu->core->capability)
> As mentioned before - this always evaluates to true, so drop the check.
> Same applies for the other dpu->core->foo checks.
>
> Still not a huge fan of the abstraction layer, but I guess you're hesitant
> on
> removing it.
>
Sometimes,  some "dpu->core->foo" maybe always need, compatibility will be
better.
eg:

    if (dpu->glb && dpu->glb->power)
        dpu->glb->power(ctx, true);
    if (dpu->glb && dpu->glb->enable)
        dpu->glb->enable(ctx);

    if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
        dpu->glb->reset(ctx);

    if (dpu->clk && dpu->clk->init)
        dpu->clk->init(ctx);
    if (dpu->clk && dpu->clk->enable)
        dpu->clk->enable(ctx);

    if (dpu->core && dpu->core->init)
        dpu->core->init(ctx);
    if (dpu->core && dpu->core->ifconfig)
        dpu->core->ifconfig(ctx);

But here, "dpu->core->capability", your are right, this always evaluates to
true
So i will remove the error checking about always being true...
> +               dpu->core->capability(&dpu->ctx, &cap);
> +
> +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> +                                 sizeof(struct sprd_dpu_layer),
GFP_KERNEL);
> +       if (!dpu->layers)
> +               return ERR_PTR(-ENOMEM);
> +
> +       for (i = 0; i < cap.max_layers; i++) {
> +
> +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> +               if (!p)
> +                       return ERR_PTR(-ENOMEM);
> +
> +               err = drm_universal_plane_init(drm, &p->plane, 1,
> +                                              &sprd_plane_funcs,
cap.fmts_ptr,
> +                                              cap.fmts_cnt, NULL,
> +                                              DRM_PLANE_TYPE_PRIMARY,
NULL);
> +               if (err) {
> +                       DRM_ERROR("fail to init primary plane\n");
> +                       return ERR_PTR(err);
> +               }
> +
> +               drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
> +
> +               sprd_plane_create_properties(p, i);
> +
> +               p->index = i;
> +               if (i == 0)
> +                       primary = &p->plane;
> +       }
> +
> +       if (p)
> +               DRM_INFO("dpu plane init ok\n");

This and nearly all the other DRM_INFO() messages look like a
debug/development
left over. Please remove them - the driver does not need to print when
functions
are successfull.


> +
> +       return primary;
> +}
> +
> +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> +       else
> +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> +}
> +
> +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> +                                       const struct drm_display_mode
*mode)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__,
DRM_MODE_ARG(mode));
> +

If needed, let's move this to core and make it a debug message. As-is it
will
cause spam for no reason.


> +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> +               dpu->mode = (struct drm_display_mode *)mode;
> +
> +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> +               dpu->mode = (struct drm_display_mode *)mode;

Casting away the constness is a bad idea.

Instead, let's move the if_type decision here, thus we can remove the
nsprd_crtc_mode_set_nofb function? This way we can also remove
sprd_dpu::mode.

> +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);

Similarly, one could derive the vm based attributes here and remove
dpu->ctx.vm.


> +       }
> +
> +       return MODE_OK;
> +}
> +
> +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> +                                  struct drm_crtc_state *old_state)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_INFO("%s()\n", __func__);
> +
More sprurious info messages - debug leftover?



> +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +
> +       DRM_DEBUG("%s()\n", __func__);
> +
Personally, I don't see the appeal in these debug messages. While a few
display
controllers have the odd piece, they are an exception in DRM.



> +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> +{
> +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> +       struct drm_device *drm = dpu->crtc.dev;
> +       struct drm_property *prop;
> +       struct drm_property_blob *blob;
> +       size_t blob_size;
> +
> +       blob_size = strlen(dpu->ctx.version) + 1;
> +
> +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> +                       dpu->ctx.version);
> +       if (IS_ERR(blob)) {
> +               DRM_ERROR("drm_property_create_blob dpu version
failed\n");
> +               return PTR_ERR(blob);
> +       }
> +
> +       /* create dpu version property */
> +       prop = drm_property_create(drm,
> +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> +               "dpu version", 0);

Note: Custom properties should be separate patches. This includes
documentation
why they are needed and references to open-source userspace.
This only need for our chips, what documentation do we need to provide?


HTH
Emil
Emil Velikov March 6, 2020, 5:14 p.m. UTC | #6
On Thu, 5 Mar 2020 at 13:15, tang pengchuan <kevin3.tang@gmail.com> wrote:
> On Tue, Mar 3, 2020 at 2:29 AM Emil Velikov <emil.l.velikov@gmail.com> wrote:

>> Have you seen a case where the 0 or default case are reached? AFAICT they will
>> never trigger. So one might as well use:
>>
>>     switch (angle) {
>>     case DRM_MODE_FOO:
>>         return DPU_LAYER_ROTATION_FOO;
>>     ...
>>     case DRM_MODE_BAR:
>>         return DPU_LAYER_ROTATION_BAR;
>>     }
>>
> Yeah, the 0 maybe unused code, i will remove it.
> But i think default is need, because userspace could give an incorrect value .
> So we need to setup a default value and doing error check.

As mentioned in the documentation [0] input (userspace) validation
should happen in atomic_check. This function here is called during
atomic_flush which is _not_ allowed to fail.



>> The default case here should be unreachable. Either it is or the upper layer (or
>> earlier code) should ensure that.
>
> There will be some differences in the formats supported by different chips, but userspace will only have one set of code.
> So it is necessary to check whether the parameters passed by the user layer are wrong. I think it is necessary

As said above - this type of issues should be checked _before_
reaching atomic_flush - aka in atomic_check.


>> > +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
>> > +                                       struct sprd_dpu *dpu)
>> > +{
>> > +       struct drm_plane *primary = NULL;
>> > +       struct sprd_plane *p = NULL;
>> > +       struct dpu_capability cap = {};
>> > +       int err, i;
>> > +
>> > +       if (dpu->core && dpu->core->capability)
>> As mentioned before - this always evaluates to true, so drop the check.
>> Same applies for the other dpu->core->foo checks.
>>
>> Still not a huge fan of the abstraction layer, but I guess you're hesitant on
>> removing it.
>
> Sometimes,  some "dpu->core->foo" maybe always need, compatibility will be better.
> eg:
>
>     if (dpu->glb && dpu->glb->power)
>         dpu->glb->power(ctx, true);
>     if (dpu->glb && dpu->glb->enable)
>         dpu->glb->enable(ctx);
>
>     if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
>         dpu->glb->reset(ctx);
>
>     if (dpu->clk && dpu->clk->init)
>         dpu->clk->init(ctx);
>     if (dpu->clk && dpu->clk->enable)
>         dpu->clk->enable(ctx);
>
>     if (dpu->core && dpu->core->init)
>         dpu->core->init(ctx);
>     if (dpu->core && dpu->core->ifconfig)
>         dpu->core->ifconfig(ctx);
>

If there are no hooks, then the whole thing is dead code. As such it
should not be included.


> >
> > Note: Custom properties should be separate patches. This includes documentation
> > why they are needed and references to open-source userspace.
> This only need for our chips, what documentation do we need to provide?
>

KMS properties should be generic. Reason being is that divergence
causes substantial overhead, and fragility, to each and every
userspace consumer. The documentation has some general notes on the
topic [1]. Don't forget the "Testing and validation" section ;-)

Although I've tried to catch everything, I might have missed a comment
or two due the HTML formatting. Please toggle to plain text [2] for
the future.

Thanks
-Emil

[0] https://www.kernel.org/doc/html/v5.5/gpu/drm-kms.html
[1] Documentation/gpu/drm-uapi.rst in particular "Open-Source
Userspace Requirements"
[2] https://smallbusiness.chron.com/reply-inline-gmail-40679.html
Kevin Tang March 7, 2020, 10 a.m. UTC | #7
Hi Emil,
Maybe here is your missing comments
I ’m really sorry, sometimes I forget to use plain text by gmail,
never make the same mistake again.

Emil Velikov <emil.l.velikov@gmail.com> 于2020年3月3日周二 上午2:29写道:
>
> Hi Kevin,
>
> There's a few small suggestions, although overall the driver looks a lot better.
>
> On Thu, 27 Feb 2020 at 08:14, Kevin Tang <kevin3.tang@gmail.com> wrote:
>
> > --- /dev/null
> > +++ b/drivers/gpu/drm/sprd/dpu/Makefile
> > @@ -0,0 +1,7 @@
> > +# SPDX-License-Identifier: GPL-2.0
> > +
> > +ifdef CONFIG_ARM64
> > +KBUILD_CFLAGS += -mstrict-align
>
>
> There are many other drivers that do not use readl/writel for register access,
> yet none has this workaround... Even those that they are exclusively ARM64.
>
> Have you tried that it's not a buggy version of GCC? At the very least, I'd
> encourage you to add a brief comment about the problem + setup.
>
> ... In general I think one should follow the suggestions from Rob Herring.
>
Yocto v2.5
aarch64-linaro-linux-gcc (Linaro GCC 7.2-2017.11) 7.2.1 20171011

Crash Stack:
/sprd/drv/dispc/dpu_r2p0.c:729
1796256 ffffff8008486650:       f803c043        stur    x3, [x2,#60]
=>Unhandled fault: alignment fault (0x96000061) at 0xffffff80098b883c

729         reg->mmu_min_ppn1 = 0;
730         reg->mmu_ppn_range1 = 0xffff;
731         reg->mmu_min_ppn2 = 0;
732         reg->mmu_ppn_range2 = 0xffff;

The above C code operation are continuous. The compiler may think that
the access can be completed by directly using two 64-bit assignment
operations, so it is optimized to 64-bit operation.

Assembly code:
=============================
/sprd/drv/dispc/dpu_r2p0.c:729
1796244 ffffff8008486638:       91200022        add     x2, x1, #0x800
/sprd/drv/dispc/dpu_r2p0.c:728
1796246 ffffff800848663c:       b908003f        str     wzr, [x1,#2048]
/sprd/drv/dispc/dpu_r2p0.c:729
1796248 ffffff8008486640:       d2dfffe3        mov     x3,
#0xffff00000000
/sprd/drv/dispc/dpu_r2p0.c:733
1796250 ffffff8008486644:       12bfffc4        mov     w4, #0x1ffff
/sprd/drv/dispc/dpu_r2p0.c:735
1796252 ffffff8008486648:       529fffe5        mov     w5, #0xffff
/sprd/drv/dispc/dpu_r2p0.c:741
1796254 ffffff800848664c:       52800000        mov     w0, #0x0
/sprd/drv/dispc/dpu_r2p0.c:729
1796256 ffffff8008486650:       f803c043        stur    x3, [x2,#60]
=>Unhandled fault: alignment fault (0x96000061) at 0xffffff80098b883c
/sprd/drv/dispc/dpu_r2p0.c:730
1796258 ffffff8008486654:       f8044043        stur    x3, [x2,#68]
/sprd/drv/dispc/dpu_r2p0.c:735
1796260 ffffff8008486658:       b901e425        str     w5, [x1,#484]
/sprd/drv/dispc/dpu_r2p0.c:733
1796262 ffffff800848665c:       b9080c24        str     w4, [x1,#2060]
1796263 ffffff8008486660:       f9400274        ldr     x20, [x19]
>
>
> > +static void dpu_dump(struct dpu_context *ctx)
> > +{
> > +       u32 *reg = (u32 *)ctx->base;
> > +       int i;
> > +
> > +       pr_info("      0          4          8          C\n");
> > +       for (i = 0; i < 256; i += 4) {
> > +               pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
> > +                       i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);
>
> Using some of the helpers from drm_print.h would be better than pr_*.
> This applies for the rest of the patch.
>
>
> > +static void dpu_clean_all(struct dpu_context *ctx)
> > +{
> > +       int i;
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       for (i = 0; i < 8; i++)
>
> This "< 8" seem pretty magical. How about "< ARRAY_SIZE(reg->layers)"
> Same logic applies through the rest of the patch.
>
>
> > +static int dpu_wait_stop_done(struct dpu_context *ctx)
> > +{
> > +       int rc;
> > +
> > +       if (ctx->stopped)
> > +               return 0;
> > +
> The stopped handling does look suspicious. Admittedly I did not look too closely
> at the dpu_flip code, which seems to require it.
>
> Let's add a small comment in the struct dpu_context::stopped declaration, why it
> is needed, if it truely is.
>
> > +       rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_stop,
> > +                                              msecs_to_jiffies(500));
> > +       ctx->evt_stop = false;
> > +
> > +       ctx->stopped = true;
> > +
> > +       if (!rc) {
> > +               pr_err("dpu wait for stop done time out!\n");
> > +               return -ETIMEDOUT;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
>
> > +static void dpu_stop(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (ctx->stopped)
> > +               return;
> > +
> > +       if (ctx->if_type == SPRD_DISPC_IF_DPI)
> > +               reg->dpu_ctrl |= BIT(1);
> > +
> > +       dpu_wait_stop_done(ctx);
> > +
> > +       pr_info("dpu stop\n");
>
> This and the dpu_run pr_info() messages can be removed.
>
>
> > +}
> > +
> > +static void dpu_run(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +
> > +       if (!ctx->stopped)
> > +               return;
> > +
> > +       reg->dpu_ctrl |= BIT(0);
> > +
> > +       ctx->stopped = false;
> > +
> > +       pr_info("dpu run\n");
> > +}
> > +
> > +static int dpu_init(struct dpu_context *ctx)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       u32 size;
> > +
> > +       reg->bg_color = 0;
> > +
> > +       size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
> > +       reg->panel_size = size;
> > +       reg->blend_size = size;
> > +
> > +       reg->dpu_cfg0 = BIT(4) | BIT(5);
> > +
> > +       reg->dpu_cfg1 = 0x004466da;
> > +       reg->dpu_cfg2 = 0;
> > +
> > +       if (ctx->stopped)
> > +               dpu_clean_all(ctx);
> > +
> > +       reg->mmu_en = 0;
> > +       reg->mmu_min_ppn1 = 0;
> > +       reg->mmu_ppn_range1 = 0xffff;
> > +       reg->mmu_min_ppn2 = 0;
> > +       reg->mmu_ppn_range2 = 0xffff;
> > +       reg->mmu_vpn_range = 0x1ffff;
> > +
> > +       reg->dpu_int_clr = 0xffff;
> > +
> > +       init_waitqueue_head(&ctx->wait_queue);
> > +
> > +       return 0;
>
> Function always returns 0. Let's make it static void dpu_init()
>
>
>
> > +static u32 to_dpu_rotation(u32 angle)
> > +{
> > +       u32 rot = DPU_LAYER_ROTATION_0;
> > +
> > +       switch (angle) {
> > +       case 0:
> > +       case DRM_MODE_ROTATE_0:
> > +               rot = DPU_LAYER_ROTATION_0;
> > +               break;
> > +       case DRM_MODE_ROTATE_90:
> > +               rot = DPU_LAYER_ROTATION_90;
> > +               break;
> > +       case DRM_MODE_ROTATE_180:
> > +               rot = DPU_LAYER_ROTATION_180;
> > +               break;
> > +       case DRM_MODE_ROTATE_270:
> > +               rot = DPU_LAYER_ROTATION_270;
> > +               break;
> > +       case DRM_MODE_REFLECT_Y:
> > +               rot = DPU_LAYER_ROTATION_180_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_90_M;
> > +               break;
> > +       case DRM_MODE_REFLECT_X:
> > +               rot = DPU_LAYER_ROTATION_0_M;
> > +               break;
> > +       case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
> > +               rot = DPU_LAYER_ROTATION_270_M;
> > +               break;
> > +       default:
> > +               pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
> > +               break;
>
> Have you seen a case where the 0 or default case are reached? AFAICT they will
> never trigger. So one might as well use:
>     switch (angle) {
>     case DRM_MODE_FOO:
>         return DPU_LAYER_ROTATION_FOO;
>     ...
>     case DRM_MODE_BAR:
>         return DPU_LAYER_ROTATION_BAR;
>     }
>
>
> > +       }
> > +
> > +       return rot;
> > +}
> > +
> > +static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
> > +{
> > +       int reg_val = 0;
> > +
> > +       /* layer enable */
> > +       reg_val |= BIT(0);
> > +
> > +       switch (format) {
> > +       case DRM_FORMAT_BGRA8888:
> > +               /* BGRA8888 -> ARGB8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_RGBX8888:
> > +       case DRM_FORMAT_RGBA8888:
> > +               /* RGBA8888 -> ABGR8888 */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ABGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_ARGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_XBGR8888:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_XRGB8888:
> > +               reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
> > +               break;
> > +       case DRM_FORMAT_BGR565:
> > +               /* rb switch */
> > +               reg_val |= BIT(10);
> > +               /* FALLTHRU */
> > +       case DRM_FORMAT_RGB565:
> > +               reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
> > +               break;
> > +       case DRM_FORMAT_NV12:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV21:
> > +               /* 2-Lane: Yuv420 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV16:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       case DRM_FORMAT_NV61:
> > +               /* 2-Lane: Yuv422 */
> > +               reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YUV420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
> > +               break;
> > +       case DRM_FORMAT_YVU420:
> > +               reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
> > +               /* Y endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
> > +               /* UV endian */
> > +               reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
> > +               break;
> > +       default:
> > +               pr_err("error: invalid format %c%c%c%c\n", format,
> > +                                               format >> 8,
> > +                                               format >> 16,
> > +                                               format >> 24);
> > +               break;
> The default case here should be unreachable. Either it is or the upper layer (or
> earlier code) should ensure that.
>
> > +       }
> > +
> > +       switch (blending) {
> > +       case DRM_MODE_BLEND_PIXEL_NONE:
> > +               /* don't do blending, maybe RGBX */
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> > +       case DRM_MODE_BLEND_COVERAGE:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Normal mode*/
> > +               reg_val &= (~BIT(16));
> > +               break;
> > +       case DRM_MODE_BLEND_PREMULTI:
> > +               /* alpha mode select - combo alpha */
> > +               reg_val |= BIT(3);
> > +               /*Pre-mult mode*/
> > +               reg_val |= BIT(16);
> > +               break;
> > +       default:
> > +               /* alpha mode select - layer alpha */
> > +               reg_val |= BIT(2);
> > +               break;
> Ditto
>
> > +       }
> > +
> > +       rotation = to_dpu_rotation(rotation);
> > +       reg_val |= (rotation & 0x7) << 20;
> > +
> > +       return reg_val;
> > +}
> > +
>
> > +static void dpu_layer(struct dpu_context *ctx,
> > +                   struct sprd_dpu_layer *hwlayer)
> > +{
> > +       struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
> > +       const struct drm_format_info *info;
> > +       struct layer_reg *layer;
> > +       u32 addr, size, offset;
> > +       int i;
> > +
> > +       layer = &reg->layers[hwlayer->index];
> > +       offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
> > +
> > +       if (hwlayer->src_w && hwlayer->src_h)
> > +               size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
> > +       else
> > +               size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
> > +
> > +       for (i = 0; i < hwlayer->planes; i++) {
> > +               addr = hwlayer->addr[i];
> > +
> > +               if (addr % 16)
> > +                       pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
> > +                               i, addr);
> > +               layer->addr[i] = addr;
> > +       }
> > +
> > +       layer->pos = offset;
> > +       layer->size = size;
> > +       layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
> > +       layer->alpha = hwlayer->alpha;
> > +
> > +       info = drm_format_info(hwlayer->format);
> > +       if (info->cpp[0] == 0) {
>
> Ditto
>
> > +               pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
> > +               return;
> > +       }
> > +
>
>
>
>
> > +static int dpu_capability(struct dpu_context *ctx,
> > +                       struct dpu_capability *cap)
> > +{
> > +       if (!cap)
> > +               return -EINVAL;
> > +
> Ensure the caller always passes cap != NULL and drop the function return type?
>
> > +       cap->max_layers = 6;
> > +       cap->fmts_ptr = primary_fmts;
> > +       cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
> > +
> > +       return 0;
> > +}
>
>
> > +static int sprd_plane_atomic_check(struct drm_plane *plane,
> > +                                 struct drm_plane_state *state)
> > +{
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
>
> Would be nice to hear from the atomic experts, how a no-op atomic_check goes
> with the overall atomic semantics.
>
>
> > +       return 0;
> > +}
> > +
>
>
> > +static void sprd_plane_atomic_disable(struct drm_plane *plane,
> > +                                    struct drm_plane_state *old_state)
> > +{
> > +       struct sprd_plane *p = to_sprd_plane(plane);
> > +
> > +       /*
> > +        * NOTE:
> > +        * The dpu->core->flip() will disable all the planes each time.
> > +        * So there is no need to impliment the atomic_disable() function.
> > +        * But this function can not be removed, because it will change
> > +        * to call atomic_update() callback instead. Which will cause
> > +        * kernel panic in sprd_plane_atomic_update().
> > +        *
> > +        * We do nothing here but just print a debug log.
> > +        */
> > +       DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
>
> Similar to the check - would be nice to see a confirmation, that this isn't
> abusing atomics in some way.
>
>
> > +}
> > +
> > +static int sprd_plane_create_properties(struct sprd_plane *p, int index)
> > +{
> > +       unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
> > +                                      BIT(DRM_MODE_BLEND_PREMULTI) |
> > +                                      BIT(DRM_MODE_BLEND_COVERAGE);
> > +
> > +       /* create rotation property */
> > +       drm_plane_create_rotation_property(&p->plane,
> > +                                          DRM_MODE_ROTATE_0,
> > +                                          DRM_MODE_ROTATE_MASK |
> > +                                          DRM_MODE_REFLECT_MASK);
> > +
> > +       /* create alpha property */
> > +       drm_plane_create_alpha_property(&p->plane);
> > +
> > +       /* create blend mode property */
> > +       drm_plane_create_blend_mode_property(&p->plane, supported_modes);
> > +
> > +       /* create zpos property */
> > +       drm_plane_create_zpos_immutable_property(&p->plane, index);
> > +
> Either check if creating the properties fail (and propagate the error) or drop
> the function return type. As-is it's in the middle making it fairly misleading.
>
> > +       return 0;
> > +}
> > +
>
>
> > +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> > +                                       struct sprd_dpu *dpu)
> > +{
> > +       struct drm_plane *primary = NULL;
> > +       struct sprd_plane *p = NULL;
> > +       struct dpu_capability cap = {};
> > +       int err, i;
> > +
> > +       if (dpu->core && dpu->core->capability)
> As mentioned before - this always evaluates to true, so drop the check.
> Same applies for the other dpu->core->foo checks.
>
> Still not a huge fan of the abstraction layer, but I guess you're hesitant on
> removing it.
Here is the complete implementation of our "sprd_dpu_init":
    if (dpu->glb && dpu->glb->power)
        dpu->glb->power(ctx, true);
    if (dpu->glb && dpu->glb->enable)
        dpu->glb->enable(ctx);

    if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
        dpu->glb->reset(ctx);

    if (dpu->clk && dpu->clk->init)
        dpu->clk->init(ctx);
    if (dpu->clk && dpu->clk->enable)
        dpu->clk->enable(ctx);

    if (dpu->core && dpu->core->init)
        dpu->core->init(ctx);
    if (dpu->core && dpu->core->ifconfig)
        dpu->core->ifconfig(ctx);

“dpu->core", "dpu->clk", "dpu->glb" checking maybe is not needed, i
will remove it.
But, Eg: "dpu->glb->power" not always need for all h/w
So in order to keep the same coding style, could i do like this:
    if (dpu->glb->power)
        dpu->glb->power(ctx, true);
    if (dpu->glb->enable)
        dpu->glb->enable(ctx);
......

But here, "dpu->core->capability", your are right, this always evaluates to true
So i will remove the error checking about always being true...
>
> > +               dpu->core->capability(&dpu->ctx, &cap);
> > +
> > +       dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
> > +                                 sizeof(struct sprd_dpu_layer), GFP_KERNEL);
> > +       if (!dpu->layers)
> > +               return ERR_PTR(-ENOMEM);
> > +
> > +       for (i = 0; i < cap.max_layers; i++) {
> > +
> > +               p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
> > +               if (!p)
> > +                       return ERR_PTR(-ENOMEM);
> > +
> > +               err = drm_universal_plane_init(drm, &p->plane, 1,
> > +                                              &sprd_plane_funcs, cap.fmts_ptr,
> > +                                              cap.fmts_cnt, NULL,
> > +                                              DRM_PLANE_TYPE_PRIMARY, NULL);
> > +               if (err) {
> > +                       DRM_ERROR("fail to init primary plane\n");
> > +                       return ERR_PTR(err);
> > +               }
> > +
> > +               drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
> > +
> > +               sprd_plane_create_properties(p, i);
> > +
> > +               p->index = i;
> > +               if (i == 0)
> > +                       primary = &p->plane;
> > +       }
> > +
> > +       if (p)
> > +               DRM_INFO("dpu plane init ok\n");
>
> This and nearly all the other DRM_INFO() messages look like a debug/development
> left over. Please remove them - the driver does not need to print when functions
> are successfull.
>
>
> > +
> > +       return primary;
> > +}
> > +
> > +static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
> > +           (dpu->mode->vdisplay == dpu->mode->vtotal))
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
> > +       else
> > +               dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
> > +}
> > +
> > +static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
> > +                                       const struct drm_display_mode *mode)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
> > +
>
> If needed, let's move this to core and make it a debug message. As-is it will
> cause spam for no reason.
>
>
> > +       if (mode->type & DRM_MODE_TYPE_DEFAULT)
> > +               dpu->mode = (struct drm_display_mode *)mode;
> > +
> > +       if (mode->type & DRM_MODE_TYPE_PREFERRED) {
> > +               dpu->mode = (struct drm_display_mode *)mode;
>
> Casting away the constness is a bad idea.
>
> Instead, let's move the if_type decision here, thus we can remove the
> nsprd_crtc_mode_set_nofb function? This way we can also remove sprd_dpu::mode.
>
>
> > +               drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
>
> Similarly, one could derive the vm based attributes here and remove dpu->ctx.vm.
>
>
> > +       }
> > +
> > +       return MODE_OK;
> > +}
> > +
> > +static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
> > +                                  struct drm_crtc_state *old_state)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_INFO("%s()\n", __func__);
> > +
> More sprurious info messages - debug leftover?
>
>
>
> > +static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +
> > +       DRM_DEBUG("%s()\n", __func__);
> > +
> Personally, I don't see the appeal in these debug messages. While a few display
> controllers have the odd piece, they are an exception in DRM.
>
>
>
> > +static int sprd_crtc_create_properties(struct drm_crtc *crtc)
> > +{
> > +       struct sprd_dpu *dpu = crtc_to_dpu(crtc);
> > +       struct drm_device *drm = dpu->crtc.dev;
> > +       struct drm_property *prop;
> > +       struct drm_property_blob *blob;
> > +       size_t blob_size;
> > +
> > +       blob_size = strlen(dpu->ctx.version) + 1;
> > +
> > +       blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
> > +                       dpu->ctx.version);
> > +       if (IS_ERR(blob)) {
> > +               DRM_ERROR("drm_property_create_blob dpu version failed\n");
> > +               return PTR_ERR(blob);
> > +       }
> > +
> > +       /* create dpu version property */
> > +       prop = drm_property_create(drm,
> > +               DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
> > +               "dpu version", 0);
>
> Note: Custom properties should be separate patches. This includes documentation
> why they are needed and references to open-source userspace.
>
>
> HTH
> Emil
Kevin Tang March 7, 2020, 1:26 p.m. UTC | #8
Emil Velikov <emil.l.velikov@gmail.com> 于2020年3月7日周六 上午1:14写道:
>
> On Thu, 5 Mar 2020 at 13:15, tang pengchuan <kevin3.tang@gmail.com> wrote:
> > On Tue, Mar 3, 2020 at 2:29 AM Emil Velikov <emil.l.velikov@gmail.com> wrote:
>
> >> Have you seen a case where the 0 or default case are reached? AFAICT they will
> >> never trigger. So one might as well use:
> >>
> >>     switch (angle) {
> >>     case DRM_MODE_FOO:
> >>         return DPU_LAYER_ROTATION_FOO;
> >>     ...
> >>     case DRM_MODE_BAR:
> >>         return DPU_LAYER_ROTATION_BAR;
> >>     }
> >>
> > Yeah, the 0 maybe unused code, i will remove it.
> > But i think default is need, because userspace could give an incorrect value .
> > So we need to setup a default value and doing error check.
>
> As mentioned in the documentation [0] input (userspace) validation
> should happen in atomic_check. This function here is called during
> atomic_flush which is _not_ allowed to fail.
In drm atomic commit codepath:
drm_atomic_commit-->drm_atomic_plane_check--->drm_plane_check_pixel_format
already helped us check DRM_FORMAT_XXX, so default laber is dead code.
Is just a waste of time and increases the complexity of the code for no reason.

We can't use "return" replace "break"
Because "switch(format)" and "switch(blending)"exist at the same
funtion, and I don't think it's a good idea to split them.
I think there are two solutions:
1. Remove default label completely
2. Add "default: // Do nothing", Eg:
int flag = value > 1000 ? 1 : 0;
swich(flag) {
    case 0:
        // do something
        break;
    case 1:
        // do something
        break;
    default: // do nothing
         break;
}

>
>
>
> >> The default case here should be unreachable. Either it is or the upper layer (or
> >> earlier code) should ensure that.
> >
> > There will be some differences in the formats supported by different chips, but userspace will only have one set of code.
> > So it is necessary to check whether the parameters passed by the user layer are wrong. I think it is necessary
>
> As said above - this type of issues should be checked _before_
> reaching atomic_flush - aka in atomic_check.
Your are right, switch(format) and switch(blending) will never reach
default label, so it's dead code.
As for rotation, we will ensure it is correct at the user layer.
>
>
> >> > +static struct drm_plane *sprd_plane_init(struct drm_device *drm,
> >> > +                                       struct sprd_dpu *dpu)
> >> > +{
> >> > +       struct drm_plane *primary = NULL;
> >> > +       struct sprd_plane *p = NULL;
> >> > +       struct dpu_capability cap = {};
> >> > +       int err, i;
> >> > +
> >> > +       if (dpu->core && dpu->core->capability)
> >> As mentioned before - this always evaluates to true, so drop the check.
> >> Same applies for the other dpu->core->foo checks.
> >>
> >> Still not a huge fan of the abstraction layer, but I guess you're hesitant on
> >> removing it.
> >
> > Sometimes,  some "dpu->core->foo" maybe always need, compatibility will be better.
> > eg:
> >
> >     if (dpu->glb && dpu->glb->power)
> >         dpu->glb->power(ctx, true);
> >     if (dpu->glb && dpu->glb->enable)
> >         dpu->glb->enable(ctx);
> >
> >     if (ctx->is_stopped && dpu->glb && dpu->glb->reset)
> >         dpu->glb->reset(ctx);
> >
> >     if (dpu->clk && dpu->clk->init)
> >         dpu->clk->init(ctx);
> >     if (dpu->clk && dpu->clk->enable)
> >         dpu->clk->enable(ctx);
> >
> >     if (dpu->core && dpu->core->init)
> >         dpu->core->init(ctx);
> >     if (dpu->core && dpu->core->ifconfig)
> >         dpu->core->ifconfig(ctx);
> >
>
> If there are no hooks, then the whole thing is dead code. As such it
> should not be included.
>
>
> > >
> > > Note: Custom properties should be separate patches. This includes documentation
> > > why they are needed and references to open-source userspace.
> > This only need for our chips, what documentation do we need to provide?
> >
>
> KMS properties should be generic. Reason being is that divergence
> causes substantial overhead, and fragility, to each and every
> userspace consumer. The documentation has some general notes on the
> topic [1]. Don't forget the "Testing and validation" section ;-)
>
> Although I've tried to catch everything, I might have missed a comment
> or two due the HTML formatting. Please toggle to plain text [2] for
> the future.
I got it
>
> Thanks
> -Emil
>
> [0] https://www.kernel.org/doc/html/v5.5/gpu/drm-kms.html
> [1] Documentation/gpu/drm-uapi.rst in particular "Open-Source
> Userspace Requirements"
> [2] https://smallbusiness.chron.com/reply-inline-gmail-40679.html
Sam Ravnborg March 7, 2020, 4:14 p.m. UTC | #9
Hi Kevin

> > > +
> > > +ifdef CONFIG_ARM64
> > > +KBUILD_CFLAGS += -mstrict-align
> >
> >
> > There are many other drivers that do not use readl/writel for register access,
> > yet none has this workaround... Even those that they are exclusively ARM64.
> >
> > Have you tried that it's not a buggy version of GCC? At the very least, I'd
> > encourage you to add a brief comment about the problem + setup.
> >
> > ... In general I think one should follow the suggestions from Rob Herring.
> >
> Yocto v2.5
> aarch64-linaro-linux-gcc (Linaro GCC 7.2-2017.11) 7.2.1 20171011
> 
> Crash Stack:
> /sprd/drv/dispc/dpu_r2p0.c:729
> 1796256 ffffff8008486650:       f803c043        stur    x3, [x2,#60]
> =>Unhandled fault: alignment fault (0x96000061) at 0xffffff80098b883c
> 
> 729         reg->mmu_min_ppn1 = 0;
> 730         reg->mmu_ppn_range1 = 0xffff;
> 731         reg->mmu_min_ppn2 = 0;
> 732         reg->mmu_ppn_range2 = 0xffff;
> 
> The above C code operation are continuous. The compiler may think that
> the access can be completed by directly using two 64-bit assignment
> operations, so it is optimized to 64-bit operation.

What you see is a side-effect of using a sturct for register access.
When you ave your code change to use readl()/writel() and friends
this is no logner a problem, and you can drop the cc flag.

	Sam
diff mbox series

Patch

diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile
index 86d95d9..88ab32a 100644
--- a/drivers/gpu/drm/sprd/Makefile
+++ b/drivers/gpu/drm/sprd/Makefile
@@ -2,4 +2,7 @@ 
 
 subdir-ccflags-y += -I$(srctree)/$(src)
 
-obj-y := sprd_drm.o
+obj-y := sprd_drm.o \
+	sprd_dpu.o
+
+obj-y += dpu/
diff --git a/drivers/gpu/drm/sprd/dpu/Makefile b/drivers/gpu/drm/sprd/dpu/Makefile
new file mode 100644
index 0000000..73bd497
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dpu/Makefile
@@ -0,0 +1,7 @@ 
+# SPDX-License-Identifier: GPL-2.0
+
+ifdef CONFIG_ARM64
+KBUILD_CFLAGS += -mstrict-align
+endif
+
+obj-y += dpu_r2p0.o
diff --git a/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
new file mode 100644
index 0000000..984fa9b
--- /dev/null
+++ b/drivers/gpu/drm/sprd/dpu/dpu_r2p0.c
@@ -0,0 +1,770 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Unisoc Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include "sprd_dpu.h"
+
+#define DISPC_INT_FBC_PLD_ERR_MASK	BIT(8)
+#define DISPC_INT_FBC_HDR_ERR_MASK	BIT(9)
+
+#define DISPC_INT_MMU_INV_WR_MASK	BIT(19)
+#define DISPC_INT_MMU_INV_RD_MASK	BIT(18)
+#define DISPC_INT_MMU_VAOR_WR_MASK	BIT(17)
+#define DISPC_INT_MMU_VAOR_RD_MASK	BIT(16)
+
+struct layer_reg {
+	u32 addr[4];
+	u32 ctrl;
+	u32 size;
+	u32 pitch;
+	u32 pos;
+	u32 alpha;
+	u32 ck;
+	u32 pallete;
+	u32 crop_start;
+};
+
+struct wb_region_reg {
+	u32 pos;
+	u32 size;
+};
+
+/* dpu controller register */
+struct dpu_reg {
+	u32 dpu_version;
+	u32 dpu_ctrl;
+	u32 dpu_cfg0;
+	u32 dpu_cfg1;
+	u32 dpu_cfg2;
+	u32 dpu_secure;
+	u32 reserved_0x0018_0x001C[2];
+	u32 panel_size;
+	u32 blend_size;
+	u32 reserved_0x0028;
+	u32 bg_color;
+	struct layer_reg layers[8];
+	u32 wb_base_addr;
+	u32 wb_ctrl;
+	u32 wb_cfg;
+	u32 wb_pitch;
+	struct wb_region_reg region[3];
+	u32 reserved_0x01D8_0x01DC[2];
+	u32 dpu_int_en;
+	u32 dpu_int_clr;
+	u32 dpu_int_sts;
+	u32 dpu_int_raw;
+	u32 dpi_ctrl;
+	u32 dpi_h_timing;
+	u32 dpi_v_timing;
+	u32 reserved_0x01FC;
+	u32 dpu_enhance_cfg;
+	u32 reserved_0x0204_0x020C[3];
+	u32 epf_epsilon;
+	u32 epf_gain0_3;
+	u32 epf_gain4_7;
+	u32 epf_diff;
+	u32 reserved_0x0220_0x023C[8];
+	u32 hsv_lut_addr;
+	u32 hsv_lut_wdata;
+	u32 hsv_lut_rdata;
+	u32 reserved_0x024C_0x027C[13];
+	u32 cm_coef01_00;
+	u32 cm_coef03_02;
+	u32 cm_coef11_10;
+	u32 cm_coef13_12;
+	u32 cm_coef21_20;
+	u32 cm_coef23_22;
+	u32 reserved_0x0298_0x02BC[10];
+	u32 slp_cfg0;
+	u32 slp_cfg1;
+	u32 reserved_0x02C8_0x02FC[14];
+	u32 gamma_lut_addr;
+	u32 gamma_lut_wdata;
+	u32 gamma_lut_rdata;
+	u32 reserved_0x030C_0x033C[13];
+	u32 checksum_en;
+	u32 checksum0_start_pos;
+	u32 checksum0_end_pos;
+	u32 checksum1_start_pos;
+	u32 checksum1_end_pos;
+	u32 checksum0_result;
+	u32 checksum1_result;
+	u32 reserved_0x035C;
+	u32 dpu_sts[18];
+	u32 reserved_0x03A8_0x03AC[2];
+	u32 dpu_fbc_cfg0;
+	u32 dpu_fbc_cfg1;
+	u32 reserved_0x03B8_0x03EC[14];
+	u32 rf_ram_addr;
+	u32 rf_ram_rdata_low;
+	u32 rf_ram_rdata_high;
+	u32 reserved_0x03FC_0x07FC[257];
+	u32 mmu_en;
+	u32 mmu_update;
+	u32 mmu_min_vpn;
+	u32 mmu_vpn_range;
+	u32 mmu_pt_addr;
+	u32 mmu_default_page;
+	u32 mmu_vaor_addr_rd;
+	u32 mmu_vaor_addr_wr;
+	u32 mmu_inv_addr_rd;
+	u32 mmu_inv_addr_wr;
+	u32 mmu_uns_addr_rd;
+	u32 mmu_uns_addr_wr;
+	u32 mmu_miss_cnt;
+	u32 mmu_pt_update_qos;
+	u32 mmu_version;
+	u32 mmu_min_ppn1;
+	u32 mmu_ppn_range1;
+	u32 mmu_min_ppn2;
+	u32 mmu_ppn_range2;
+	u32 mmu_vpn_paor_rd;
+	u32 mmu_vpn_paor_wr;
+	u32 mmu_ppn_paor_rd;
+	u32 mmu_ppn_paor_wr;
+	u32 mmu_reg_au_manage;
+	u32 mmu_page_rd_ch;
+	u32 mmu_page_wr_ch;
+	u32 mmu_read_page_cmd_cnt;
+	u32 mmu_read_page_latency_cnt;
+	u32 mmu_page_max_latency;
+};
+
+static void dpu_dump(struct dpu_context *ctx)
+{
+	u32 *reg = (u32 *)ctx->base;
+	int i;
+
+	pr_info("      0          4          8          C\n");
+	for (i = 0; i < 256; i += 4) {
+		pr_info("%04x: 0x%08x 0x%08x 0x%08x 0x%08x\n",
+			i * 4, reg[i], reg[i + 1], reg[i + 2], reg[i + 3]);
+	}
+}
+
+static u32 check_mmu_isr(struct dpu_context *ctx, u32 reg_val)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	u32 mmu_mask = DISPC_INT_MMU_VAOR_RD_MASK |
+			DISPC_INT_MMU_VAOR_WR_MASK |
+			DISPC_INT_MMU_INV_RD_MASK |
+			DISPC_INT_MMU_INV_WR_MASK;
+	u32 val = reg_val & mmu_mask;
+
+	if (val) {
+		pr_err("--- iommu interrupt err: 0x%04x ---\n", val);
+
+		pr_err("iommu invalid read error, addr: 0x%08x\n",
+			reg->mmu_inv_addr_rd);
+		pr_err("iommu invalid write error, addr: 0x%08x\n",
+			reg->mmu_inv_addr_wr);
+		pr_err("iommu va out of range read error, addr: 0x%08x\n",
+			reg->mmu_vaor_addr_rd);
+		pr_err("iommu va out of range write error, addr: 0x%08x\n",
+			reg->mmu_vaor_addr_wr);
+		pr_err("BUG: iommu failure at %s:%d/%s()!\n",
+			__FILE__, __LINE__, __func__);
+
+		dpu_dump(ctx);
+	}
+
+	return val;
+}
+
+static void dpu_clean_all(struct dpu_context *ctx)
+{
+	int i;
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	for (i = 0; i < 8; i++)
+		reg->layers[i].ctrl = 0;
+}
+
+static u32 dpu_isr(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	u32 reg_val, int_mask = 0;
+
+	reg_val = reg->dpu_int_sts;
+
+	/* disable err interrupt */
+	if (reg_val & DISPC_INT_ERR_MASK)
+		int_mask |= DISPC_INT_ERR_MASK;
+
+	/* dpu update done isr */
+	if (reg_val & DISPC_INT_UPDATE_DONE_MASK) {
+		ctx->evt_update = true;
+		wake_up_interruptible_all(&ctx->wait_queue);
+	}
+
+	/* dpu stop done isr */
+	if (reg_val & DISPC_INT_DONE_MASK) {
+		ctx->evt_stop = true;
+		wake_up_interruptible_all(&ctx->wait_queue);
+	}
+
+	/* dpu ifbc payload error isr */
+	if (reg_val & DISPC_INT_FBC_PLD_ERR_MASK) {
+		int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
+		pr_err("dpu ifbc payload error\n");
+	}
+
+	/* dpu ifbc header error isr */
+	if (reg_val & DISPC_INT_FBC_HDR_ERR_MASK) {
+		int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
+		pr_err("dpu ifbc header error\n");
+	}
+
+	int_mask |= check_mmu_isr(ctx, reg_val);
+
+	reg->dpu_int_clr = reg_val;
+	reg->dpu_int_en &= ~int_mask;
+
+	return reg_val;
+}
+
+static int dpu_wait_stop_done(struct dpu_context *ctx)
+{
+	int rc;
+
+	if (ctx->stopped)
+		return 0;
+
+	rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_stop,
+					       msecs_to_jiffies(500));
+	ctx->evt_stop = false;
+
+	ctx->stopped = true;
+
+	if (!rc) {
+		pr_err("dpu wait for stop done time out!\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static int dpu_wait_update_done(struct dpu_context *ctx)
+{
+	int rc;
+
+	ctx->evt_update = false;
+
+	rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_update,
+					       msecs_to_jiffies(500));
+
+	if (!rc) {
+		pr_err("dpu wait for reg update done time out!\n");
+		return -ETIMEDOUT;
+	}
+
+	return 0;
+}
+
+static void dpu_stop(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	if (ctx->stopped)
+		return;
+
+	if (ctx->if_type == SPRD_DISPC_IF_DPI)
+		reg->dpu_ctrl |= BIT(1);
+
+	dpu_wait_stop_done(ctx);
+
+	pr_info("dpu stop\n");
+}
+
+static void dpu_run(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	if (!ctx->stopped)
+		return;
+
+	reg->dpu_ctrl |= BIT(0);
+
+	ctx->stopped = false;
+
+	pr_info("dpu run\n");
+}
+
+static int dpu_init(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	u32 size;
+
+	reg->bg_color = 0;
+
+	size = (ctx->vm.vactive << 16) | ctx->vm.hactive;
+	reg->panel_size = size;
+	reg->blend_size = size;
+
+	reg->dpu_cfg0 = BIT(4) | BIT(5);
+
+	reg->dpu_cfg1 = 0x004466da;
+	reg->dpu_cfg2 = 0;
+
+	if (ctx->stopped)
+		dpu_clean_all(ctx);
+
+	reg->mmu_en = 0;
+	reg->mmu_min_ppn1 = 0;
+	reg->mmu_ppn_range1 = 0xffff;
+	reg->mmu_min_ppn2 = 0;
+	reg->mmu_ppn_range2 = 0xffff;
+	reg->mmu_vpn_range = 0x1ffff;
+
+	reg->dpu_int_clr = 0xffff;
+
+	init_waitqueue_head(&ctx->wait_queue);
+
+	return 0;
+}
+
+static void dpu_fini(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	reg->dpu_int_en = 0;
+	reg->dpu_int_clr = 0xff;
+}
+
+enum {
+	DPU_LAYER_FORMAT_YUV422_2PLANE,
+	DPU_LAYER_FORMAT_YUV420_2PLANE,
+	DPU_LAYER_FORMAT_YUV420_3PLANE,
+	DPU_LAYER_FORMAT_ARGB8888,
+	DPU_LAYER_FORMAT_RGB565,
+	DPU_LAYER_FORMAT_XFBC_ARGB8888 = 8,
+	DPU_LAYER_FORMAT_XFBC_RGB565,
+	DPU_LAYER_FORMAT_MAX_TYPES,
+};
+
+enum {
+	DPU_LAYER_ROTATION_0,
+	DPU_LAYER_ROTATION_90,
+	DPU_LAYER_ROTATION_180,
+	DPU_LAYER_ROTATION_270,
+	DPU_LAYER_ROTATION_0_M,
+	DPU_LAYER_ROTATION_90_M,
+	DPU_LAYER_ROTATION_180_M,
+	DPU_LAYER_ROTATION_270_M,
+};
+
+static u32 to_dpu_rotation(u32 angle)
+{
+	u32 rot = DPU_LAYER_ROTATION_0;
+
+	switch (angle) {
+	case 0:
+	case DRM_MODE_ROTATE_0:
+		rot = DPU_LAYER_ROTATION_0;
+		break;
+	case DRM_MODE_ROTATE_90:
+		rot = DPU_LAYER_ROTATION_90;
+		break;
+	case DRM_MODE_ROTATE_180:
+		rot = DPU_LAYER_ROTATION_180;
+		break;
+	case DRM_MODE_ROTATE_270:
+		rot = DPU_LAYER_ROTATION_270;
+		break;
+	case DRM_MODE_REFLECT_Y:
+		rot = DPU_LAYER_ROTATION_180_M;
+		break;
+	case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90):
+		rot = DPU_LAYER_ROTATION_90_M;
+		break;
+	case DRM_MODE_REFLECT_X:
+		rot = DPU_LAYER_ROTATION_0_M;
+		break;
+	case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90):
+		rot = DPU_LAYER_ROTATION_270_M;
+		break;
+	default:
+		pr_err("rotation convert unsupport angle (drm)= 0x%x\n", angle);
+		break;
+	}
+
+	return rot;
+}
+
+static u32 dpu_img_ctrl(u32 format, u32 blending, u32 rotation)
+{
+	int reg_val = 0;
+
+	/* layer enable */
+	reg_val |= BIT(0);
+
+	switch (format) {
+	case DRM_FORMAT_BGRA8888:
+		/* BGRA8888 -> ARGB8888 */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+		reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+		break;
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+		/* RGBA8888 -> ABGR8888 */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+		/* FALLTHRU */
+	case DRM_FORMAT_ABGR8888:
+		/* rb switch */
+		reg_val |= BIT(10);
+		/* FALLTHRU */
+	case DRM_FORMAT_ARGB8888:
+		reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+		break;
+	case DRM_FORMAT_XBGR8888:
+		/* rb switch */
+		reg_val |= BIT(10);
+		/* FALLTHRU */
+	case DRM_FORMAT_XRGB8888:
+		reg_val |= (DPU_LAYER_FORMAT_ARGB8888 << 4);
+		break;
+	case DRM_FORMAT_BGR565:
+		/* rb switch */
+		reg_val |= BIT(10);
+		/* FALLTHRU */
+	case DRM_FORMAT_RGB565:
+		reg_val |= (DPU_LAYER_FORMAT_RGB565 << 4);
+		break;
+	case DRM_FORMAT_NV12:
+		/* 2-Lane: Yuv420 */
+		reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+		break;
+	case DRM_FORMAT_NV21:
+		/* 2-Lane: Yuv420 */
+		reg_val |= DPU_LAYER_FORMAT_YUV420_2PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+		break;
+	case DRM_FORMAT_NV16:
+		/* 2-Lane: Yuv422 */
+		reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+		break;
+	case DRM_FORMAT_NV61:
+		/* 2-Lane: Yuv422 */
+		reg_val |= DPU_LAYER_FORMAT_YUV422_2PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+		break;
+	case DRM_FORMAT_YUV420:
+		reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 10;
+		break;
+	case DRM_FORMAT_YVU420:
+		reg_val |= DPU_LAYER_FORMAT_YUV420_3PLANE << 4;
+		/* Y endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B0B1B2B3 << 8;
+		/* UV endian */
+		reg_val |= SPRD_IMG_DATA_ENDIAN_B3B2B1B0 << 10;
+		break;
+	default:
+		pr_err("error: invalid format %c%c%c%c\n", format,
+						format >> 8,
+						format >> 16,
+						format >> 24);
+		break;
+	}
+
+	switch (blending) {
+	case DRM_MODE_BLEND_PIXEL_NONE:
+		/* don't do blending, maybe RGBX */
+		/* alpha mode select - layer alpha */
+		reg_val |= BIT(2);
+		break;
+	case DRM_MODE_BLEND_COVERAGE:
+		/* alpha mode select - combo alpha */
+		reg_val |= BIT(3);
+		/*Normal mode*/
+		reg_val &= (~BIT(16));
+		break;
+	case DRM_MODE_BLEND_PREMULTI:
+		/* alpha mode select - combo alpha */
+		reg_val |= BIT(3);
+		/*Pre-mult mode*/
+		reg_val |= BIT(16);
+		break;
+	default:
+		/* alpha mode select - layer alpha */
+		reg_val |= BIT(2);
+		break;
+	}
+
+	rotation = to_dpu_rotation(rotation);
+	reg_val |= (rotation & 0x7) << 20;
+
+	return reg_val;
+}
+
+static void dpu_bgcolor(struct dpu_context *ctx, u32 color)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	if (ctx->if_type == SPRD_DISPC_IF_EDPI)
+		dpu_wait_stop_done(ctx);
+
+	reg->bg_color = color;
+
+	dpu_clean_all(ctx);
+
+	if ((ctx->if_type == SPRD_DISPC_IF_DPI) && !ctx->stopped) {
+		reg->dpu_ctrl |= BIT(2);
+		dpu_wait_update_done(ctx);
+	} else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+		reg->dpu_ctrl |= BIT(0);
+		ctx->stopped = false;
+	}
+}
+
+static void dpu_layer(struct dpu_context *ctx,
+		    struct sprd_dpu_layer *hwlayer)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	const struct drm_format_info *info;
+	struct layer_reg *layer;
+	u32 addr, size, offset;
+	int i;
+
+	layer = &reg->layers[hwlayer->index];
+	offset = (hwlayer->dst_x & 0xffff) | ((hwlayer->dst_y) << 16);
+
+	if (hwlayer->src_w && hwlayer->src_h)
+		size = (hwlayer->src_w & 0xffff) | ((hwlayer->src_h) << 16);
+	else
+		size = (hwlayer->dst_w & 0xffff) | ((hwlayer->dst_h) << 16);
+
+	for (i = 0; i < hwlayer->planes; i++) {
+		addr = hwlayer->addr[i];
+
+		if (addr % 16)
+			pr_err("layer addr[%d] is not 16 bytes align, it's 0x%08x\n",
+				i, addr);
+		layer->addr[i] = addr;
+	}
+
+	layer->pos = offset;
+	layer->size = size;
+	layer->crop_start = (hwlayer->src_y << 16) | hwlayer->src_x;
+	layer->alpha = hwlayer->alpha;
+
+	info = drm_format_info(hwlayer->format);
+	if (info->cpp[0] == 0) {
+		pr_err("layer[%d] bytes per pixel is invalid\n", hwlayer->index);
+		return;
+	}
+
+	if (hwlayer->planes == 3)
+		/* UV pitch is 1/2 of Y pitch*/
+		layer->pitch = (hwlayer->pitch[0] / info->cpp[0]) |
+				(hwlayer->pitch[0] / info->cpp[0] << 15);
+	else
+		layer->pitch = hwlayer->pitch[0] / info->cpp[0];
+
+	layer->ctrl = dpu_img_ctrl(hwlayer->format, hwlayer->blending,
+		hwlayer->rotation);
+
+	pr_debug("dst_x = %d, dst_y = %d, dst_w = %d, dst_h = %d\n",
+				hwlayer->dst_x, hwlayer->dst_y,
+				hwlayer->dst_w, hwlayer->dst_h);
+	pr_debug("start_x = %d, start_y = %d, start_w = %d, start_h = %d\n",
+				hwlayer->src_x, hwlayer->src_y,
+				hwlayer->src_w, hwlayer->src_h);
+}
+
+static void dpu_flip(struct dpu_context *ctx,
+		     struct sprd_dpu_layer layers[], u8 count)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	int i;
+
+	/*
+	 * Make sure the dpu is in stop status. DPU_R2P0 has no shadow
+	 * registers in EDPI mode. So the config registers can only be
+	 * updated in the rising edge of DPU_RUN bit.
+	 */
+	if (ctx->if_type == SPRD_DISPC_IF_EDPI)
+		dpu_wait_stop_done(ctx);
+
+	/* reset the bgcolor to black */
+	reg->bg_color = 0;
+
+	/* disable all the layers */
+	dpu_clean_all(ctx);
+
+	/* start configure dpu layers */
+	for (i = 0; i < count; i++)
+		dpu_layer(ctx, &layers[i]);
+
+	/* update trigger and wait */
+	if (ctx->if_type == SPRD_DISPC_IF_DPI) {
+		if (!ctx->stopped) {
+			reg->dpu_ctrl |= BIT(2);
+			dpu_wait_update_done(ctx);
+		}
+
+		reg->dpu_int_en |= DISPC_INT_ERR_MASK;
+
+	} else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+		reg->dpu_ctrl |= BIT(0);
+
+		ctx->stopped = false;
+	}
+
+	/*
+	 * If the following interrupt was disabled in isr,
+	 * re-enable it.
+	 */
+	reg->dpu_int_en |= DISPC_INT_FBC_PLD_ERR_MASK |
+			   DISPC_INT_FBC_HDR_ERR_MASK |
+			   DISPC_INT_MMU_VAOR_RD_MASK |
+			   DISPC_INT_MMU_VAOR_WR_MASK |
+			   DISPC_INT_MMU_INV_RD_MASK |
+			   DISPC_INT_MMU_INV_WR_MASK;
+}
+
+static void dpu_dpi_init(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+	u32 int_mask = 0;
+
+	if (ctx->if_type == SPRD_DISPC_IF_DPI) {
+		/* use dpi as interface */
+		reg->dpu_cfg0 &= ~BIT(0);
+
+		/* disable Halt function for SPRD DSI */
+		reg->dpi_ctrl &= ~BIT(16);
+
+		/* select te from external pad */
+		reg->dpi_ctrl |= BIT(10);
+
+		/* set dpi timing */
+		reg->dpi_h_timing = (ctx->vm.hsync_len << 0) |
+				    (ctx->vm.hback_porch << 8) |
+				    (ctx->vm.hfront_porch << 20);
+		reg->dpi_v_timing = (ctx->vm.vsync_len << 0) |
+				    (ctx->vm.vback_porch << 8) |
+				    (ctx->vm.vfront_porch << 20);
+		if (ctx->vm.vsync_len + ctx->vm.vback_porch < 32)
+			pr_warn("Warning: (vsync + vbp) < 32, "
+				"underflow risk!\n");
+
+		/* enable dpu update done INT */
+		int_mask |= DISPC_INT_UPDATE_DONE_MASK;
+		/* enable dpu DONE  INT */
+		int_mask |= DISPC_INT_DONE_MASK;
+		/* enable dpu dpi vsync */
+		int_mask |= DISPC_INT_DPI_VSYNC_MASK;
+		/* enable dpu TE INT */
+		int_mask |= DISPC_INT_TE_MASK;
+		/* enable underflow err INT */
+		int_mask |= DISPC_INT_ERR_MASK;
+		/* enable write back done INT */
+		int_mask |= DISPC_INT_WB_DONE_MASK;
+		/* enable write back fail INT */
+		int_mask |= DISPC_INT_WB_FAIL_MASK;
+
+	} else if (ctx->if_type == SPRD_DISPC_IF_EDPI) {
+		/* use edpi as interface */
+		reg->dpu_cfg0 |= BIT(0);
+
+		/* use external te */
+		reg->dpi_ctrl |= BIT(10);
+
+		/* enable te */
+		reg->dpi_ctrl |= BIT(8);
+
+		/* enable stop DONE INT */
+		int_mask |= DISPC_INT_DONE_MASK;
+		/* enable TE INT */
+		int_mask |= DISPC_INT_TE_MASK;
+	}
+
+	/* enable ifbc payload error INT */
+	int_mask |= DISPC_INT_FBC_PLD_ERR_MASK;
+	/* enable ifbc header error INT */
+	int_mask |= DISPC_INT_FBC_HDR_ERR_MASK;
+	/* enable iommu va out of range read error INT */
+	int_mask |= DISPC_INT_MMU_VAOR_RD_MASK;
+	/* enable iommu va out of range write error INT */
+	int_mask |= DISPC_INT_MMU_VAOR_WR_MASK;
+	/* enable iommu invalid read error INT */
+	int_mask |= DISPC_INT_MMU_INV_RD_MASK;
+	/* enable iommu invalid write error INT */
+	int_mask |= DISPC_INT_MMU_INV_WR_MASK;
+
+	reg->dpu_int_en = int_mask;
+}
+
+static void enable_vsync(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	reg->dpu_int_en |= DISPC_INT_DPI_VSYNC_MASK;
+}
+
+static void disable_vsync(struct dpu_context *ctx)
+{
+	struct dpu_reg *reg = (struct dpu_reg *)ctx->base;
+
+	reg->dpu_int_en &= ~DISPC_INT_DPI_VSYNC_MASK;
+}
+
+static const u32 primary_fmts[] = {
+	DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
+	DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+	DRM_FORMAT_NV16, DRM_FORMAT_NV61,
+	DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
+};
+
+static int dpu_capability(struct dpu_context *ctx,
+			struct dpu_capability *cap)
+{
+	if (!cap)
+		return -EINVAL;
+
+	cap->max_layers = 6;
+	cap->fmts_ptr = primary_fmts;
+	cap->fmts_cnt = ARRAY_SIZE(primary_fmts);
+
+	return 0;
+}
+
+const struct dpu_core_ops sharkl3_dpu_core_ops = {
+	.init = dpu_init,
+	.fini = dpu_fini,
+	.run = dpu_run,
+	.stop = dpu_stop,
+	.isr = dpu_isr,
+	.ifconfig = dpu_dpi_init,
+	.capability = dpu_capability,
+	.flip = dpu_flip,
+	.bg_color = dpu_bgcolor,
+	.enable_vsync = enable_vsync,
+	.disable_vsync = disable_vsync,
+};
diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c
new file mode 100644
index 0000000..f122b0e
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dpu.c
@@ -0,0 +1,586 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Unisoc Inc.
+ */
+
+#include <linux/component.h>
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "sprd_drm.h"
+#include "sprd_dpu.h"
+
+struct sprd_plane {
+	struct drm_plane plane;
+	u32 index;
+};
+
+static int sprd_dpu_init(struct sprd_dpu *dpu);
+static int sprd_dpu_fini(struct sprd_dpu *dpu);
+
+static inline struct sprd_plane *to_sprd_plane(struct drm_plane *plane)
+{
+	return container_of(plane, struct sprd_plane, plane);
+}
+
+static int sprd_plane_atomic_check(struct drm_plane *plane,
+				  struct drm_plane_state *state)
+{
+	DRM_DEBUG("%s()\n", __func__);
+
+	return 0;
+}
+
+static void sprd_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_plane_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+	struct drm_framebuffer *fb = plane->state->fb;
+	struct drm_gem_cma_object *cma_obj;
+	struct sprd_plane *p = to_sprd_plane(plane);
+	struct sprd_dpu *dpu = crtc_to_dpu(plane->state->crtc);
+	struct sprd_dpu_layer *layer = &dpu->layers[p->index];
+	int i;
+
+	if (plane->state->crtc->state->active_changed) {
+		DRM_DEBUG("resume or suspend, no need to update plane\n");
+		return;
+	}
+
+	layer->index = p->index;
+	layer->src_x = state->src_x >> 16;
+	layer->src_y = state->src_y >> 16;
+	layer->src_w = state->src_w >> 16;
+	layer->src_h = state->src_h >> 16;
+	layer->dst_x = state->crtc_x;
+	layer->dst_y = state->crtc_y;
+	layer->dst_w = state->crtc_w;
+	layer->dst_h = state->crtc_h;
+	layer->alpha = state->alpha;
+	layer->blending = state->pixel_blend_mode;
+	layer->rotation = state->rotation;
+	layer->planes = fb->format->num_planes;
+	layer->format = fb->format->format;
+
+	DRM_DEBUG("%s() alpha = %u, blending = %u, rotation = %u\n",
+		  __func__, layer->alpha, layer->blending, layer->rotation);
+
+	for (i = 0; i < layer->planes; i++) {
+		cma_obj = drm_fb_cma_get_gem_obj(fb, i);
+		layer->addr[i] = cma_obj->paddr + fb->offsets[i];
+		layer->pitch[i] = fb->pitches[i];
+	}
+
+	dpu->pending_planes++;
+}
+
+static void sprd_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_plane_state *old_state)
+{
+	struct sprd_plane *p = to_sprd_plane(plane);
+
+	/*
+	 * NOTE:
+	 * The dpu->core->flip() will disable all the planes each time.
+	 * So there is no need to impliment the atomic_disable() function.
+	 * But this function can not be removed, because it will change
+	 * to call atomic_update() callback instead. Which will cause
+	 * kernel panic in sprd_plane_atomic_update().
+	 *
+	 * We do nothing here but just print a debug log.
+	 */
+	DRM_DEBUG("%s() layer_id = %u\n", __func__, p->index);
+}
+
+static int sprd_plane_create_properties(struct sprd_plane *p, int index)
+{
+	unsigned int supported_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+				       BIT(DRM_MODE_BLEND_PREMULTI) |
+				       BIT(DRM_MODE_BLEND_COVERAGE);
+
+	/* create rotation property */
+	drm_plane_create_rotation_property(&p->plane,
+					   DRM_MODE_ROTATE_0,
+					   DRM_MODE_ROTATE_MASK |
+					   DRM_MODE_REFLECT_MASK);
+
+	/* create alpha property */
+	drm_plane_create_alpha_property(&p->plane);
+
+	/* create blend mode property */
+	drm_plane_create_blend_mode_property(&p->plane, supported_modes);
+
+	/* create zpos property */
+	drm_plane_create_zpos_immutable_property(&p->plane, index);
+
+	return 0;
+}
+
+static const struct drm_plane_helper_funcs sprd_plane_helper_funcs = {
+	.atomic_check = sprd_plane_atomic_check,
+	.atomic_update = sprd_plane_atomic_update,
+	.atomic_disable = sprd_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs sprd_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 struct drm_plane *sprd_plane_init(struct drm_device *drm,
+					struct sprd_dpu *dpu)
+{
+	struct drm_plane *primary = NULL;
+	struct sprd_plane *p = NULL;
+	struct dpu_capability cap = {};
+	int err, i;
+
+	if (dpu->core && dpu->core->capability)
+		dpu->core->capability(&dpu->ctx, &cap);
+
+	dpu->layers = devm_kcalloc(drm->dev, cap.max_layers,
+				  sizeof(struct sprd_dpu_layer), GFP_KERNEL);
+	if (!dpu->layers)
+		return ERR_PTR(-ENOMEM);
+
+	for (i = 0; i < cap.max_layers; i++) {
+
+		p = devm_kzalloc(drm->dev, sizeof(*p), GFP_KERNEL);
+		if (!p)
+			return ERR_PTR(-ENOMEM);
+
+		err = drm_universal_plane_init(drm, &p->plane, 1,
+					       &sprd_plane_funcs, cap.fmts_ptr,
+					       cap.fmts_cnt, NULL,
+					       DRM_PLANE_TYPE_PRIMARY, NULL);
+		if (err) {
+			DRM_ERROR("fail to init primary plane\n");
+			return ERR_PTR(err);
+		}
+
+		drm_plane_helper_add(&p->plane, &sprd_plane_helper_funcs);
+
+		sprd_plane_create_properties(p, i);
+
+		p->index = i;
+		if (i == 0)
+			primary = &p->plane;
+	}
+
+	if (p)
+		DRM_INFO("dpu plane init ok\n");
+
+	return primary;
+}
+
+static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	if ((dpu->mode->hdisplay == dpu->mode->htotal) ||
+	    (dpu->mode->vdisplay == dpu->mode->vtotal))
+		dpu->ctx.if_type = SPRD_DISPC_IF_EDPI;
+	else
+		dpu->ctx.if_type = SPRD_DISPC_IF_DPI;
+}
+
+static enum drm_mode_status sprd_crtc_mode_valid(struct drm_crtc *crtc,
+					const struct drm_display_mode *mode)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	DRM_INFO("%s() mode: "DRM_MODE_FMT"\n", __func__, DRM_MODE_ARG(mode));
+
+	if (mode->type & DRM_MODE_TYPE_DEFAULT)
+		dpu->mode = (struct drm_display_mode *)mode;
+
+	if (mode->type & DRM_MODE_TYPE_PREFERRED) {
+		dpu->mode = (struct drm_display_mode *)mode;
+		drm_display_mode_to_videomode(dpu->mode, &dpu->ctx.vm);
+	}
+
+	return MODE_OK;
+}
+
+static void sprd_crtc_atomic_enable(struct drm_crtc *crtc,
+				   struct drm_crtc_state *old_state)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	DRM_INFO("%s()\n", __func__);
+
+	sprd_dpu_init(dpu);
+
+	enable_irq(dpu->ctx.irq);
+
+	drm_crtc_vblank_on(crtc);
+}
+
+static void sprd_crtc_atomic_disable(struct drm_crtc *crtc,
+				    struct drm_crtc_state *old_state)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+	struct drm_device *drm = dpu->crtc.dev;
+
+	DRM_INFO("%s()\n", __func__);
+
+	drm_crtc_vblank_off(crtc);
+
+	disable_irq(dpu->ctx.irq);
+
+	sprd_dpu_fini(dpu);
+
+	spin_lock_irq(&drm->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&drm->event_lock);
+}
+
+static int sprd_crtc_atomic_check(struct drm_crtc *crtc,
+				 struct drm_crtc_state *state)
+{
+	DRM_DEBUG("%s()\n", __func__);
+
+	return 0;
+}
+
+static void sprd_crtc_atomic_begin(struct drm_crtc *crtc,
+				  struct drm_crtc_state *old_state)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	DRM_DEBUG("%s()\n", __func__);
+
+	memset(dpu->layers, 0, sizeof(*dpu->layers) * dpu->pending_planes);
+
+	dpu->pending_planes = 0;
+}
+
+static void sprd_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_crtc_state *old_state)
+
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+	struct drm_device *drm = dpu->crtc.dev;
+
+	DRM_DEBUG("%s()\n", __func__);
+
+	if (dpu->core && dpu->core->flip && dpu->pending_planes)
+		dpu->core->flip(&dpu->ctx, dpu->layers, dpu->pending_planes);
+
+	spin_lock_irq(&drm->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&drm->event_lock);
+}
+
+static int sprd_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	DRM_DEBUG("%s()\n", __func__);
+
+	if (dpu->core && dpu->core->enable_vsync)
+		dpu->core->enable_vsync(&dpu->ctx);
+
+	return 0;
+}
+
+static void sprd_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+
+	DRM_DEBUG("%s()\n", __func__);
+
+	if (dpu->core && dpu->core->disable_vsync)
+		dpu->core->disable_vsync(&dpu->ctx);
+}
+
+static int sprd_crtc_create_properties(struct drm_crtc *crtc)
+{
+	struct sprd_dpu *dpu = crtc_to_dpu(crtc);
+	struct drm_device *drm = dpu->crtc.dev;
+	struct drm_property *prop;
+	struct drm_property_blob *blob;
+	size_t blob_size;
+
+	blob_size = strlen(dpu->ctx.version) + 1;
+
+	blob = drm_property_create_blob(dpu->crtc.dev, blob_size,
+			dpu->ctx.version);
+	if (IS_ERR(blob)) {
+		DRM_ERROR("drm_property_create_blob dpu version failed\n");
+		return PTR_ERR(blob);
+	}
+
+	/* create dpu version property */
+	prop = drm_property_create(drm,
+		DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_BLOB,
+		"dpu version", 0);
+	if (!prop) {
+		DRM_ERROR("drm_property_create dpu version failed\n");
+		return -ENOMEM;
+	}
+	drm_object_attach_property(&crtc->base, prop, blob->base.id);
+
+	return 0;
+}
+
+static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = {
+	.mode_set_nofb	= sprd_crtc_mode_set_nofb,
+	.mode_valid	= sprd_crtc_mode_valid,
+	.atomic_check	= sprd_crtc_atomic_check,
+	.atomic_begin	= sprd_crtc_atomic_begin,
+	.atomic_flush	= sprd_crtc_atomic_flush,
+	.atomic_enable	= sprd_crtc_atomic_enable,
+	.atomic_disable	= sprd_crtc_atomic_disable,
+};
+
+static const struct drm_crtc_funcs sprd_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	= sprd_crtc_enable_vblank,
+	.disable_vblank	= sprd_crtc_disable_vblank,
+};
+
+static int sprd_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
+			 struct drm_plane *primary)
+{
+	struct device_node *port;
+	int err;
+
+	/*
+	 * set crtc port so that drm_of_find_possible_crtcs call works
+	 */
+	port = of_parse_phandle(drm->dev->of_node, "ports", 0);
+	if (!port) {
+		DRM_ERROR("find 'ports' phandle of %s failed\n",
+			  drm->dev->of_node->full_name);
+		return -EINVAL;
+	}
+	of_node_put(port);
+	crtc->port = port;
+
+	err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
+					&sprd_crtc_funcs, NULL);
+	if (err) {
+		DRM_ERROR("failed to init crtc.\n");
+		return err;
+	}
+
+	drm_mode_crtc_set_gamma_size(crtc, 256);
+
+	drm_crtc_helper_add(crtc, &sprd_crtc_helper_funcs);
+
+	sprd_crtc_create_properties(crtc);
+
+	DRM_INFO("%s() ok\n", __func__);
+	return 0;
+}
+
+static int sprd_dpu_init(struct sprd_dpu *dpu)
+{
+	struct dpu_context *ctx = &dpu->ctx;
+
+	if (dpu->core && dpu->core->init)
+		dpu->core->init(ctx);
+	if (dpu->core && dpu->core->ifconfig)
+		dpu->core->ifconfig(ctx);
+
+	return 0;
+}
+
+static int sprd_dpu_fini(struct sprd_dpu *dpu)
+{
+	struct dpu_context *ctx = &dpu->ctx;
+
+	if (dpu->core && dpu->core->fini)
+		dpu->core->fini(ctx);
+
+	return 0;
+}
+
+static irqreturn_t sprd_dpu_isr(int irq, void *data)
+{
+	struct sprd_dpu *dpu = data;
+	struct dpu_context *ctx = &dpu->ctx;
+	u32 int_mask = 0;
+
+	if (dpu->core && dpu->core->isr)
+		int_mask = dpu->core->isr(ctx);
+
+	if (int_mask & DISPC_INT_ERR_MASK)
+		DRM_WARN("Warning: dpu underflow!\n");
+
+	if ((int_mask & DISPC_INT_DPI_VSYNC_MASK))
+		drm_crtc_handle_vblank(&dpu->crtc);
+
+	return IRQ_HANDLED;
+}
+
+static int sprd_dpu_irq_request(struct sprd_dpu *dpu)
+{
+	int err;
+	int irq_num;
+
+	irq_num = irq_of_parse_and_map(dpu->dev.of_node, 0);
+	if (!irq_num) {
+		DRM_ERROR("error: dpu parse irq num failed\n");
+		return -EINVAL;
+	}
+	DRM_INFO("dpu irq_num = %d\n", irq_num);
+
+	irq_set_status_flags(irq_num, IRQ_NOAUTOEN);
+	err = devm_request_irq(&dpu->dev, irq_num, sprd_dpu_isr,
+					0, "DISPC", dpu);
+	if (err) {
+		DRM_ERROR("error: dpu request irq failed\n");
+		return -EINVAL;
+	}
+	dpu->ctx.irq = irq_num;
+
+	return 0;
+}
+
+static int sprd_dpu_bind(struct device *dev, struct device *master, void *data)
+{
+	struct drm_device *drm = data;
+	struct sprd_dpu *dpu = dev_get_drvdata(dev);
+	struct drm_plane *plane;
+	int err;
+
+	DRM_INFO("%s()\n", __func__);
+
+	plane = sprd_plane_init(drm, dpu);
+	if (IS_ERR_OR_NULL(plane)) {
+		err = PTR_ERR(plane);
+		return err;
+	}
+
+	err = sprd_crtc_init(drm, &dpu->crtc, plane);
+	if (err)
+		return err;
+
+	sprd_dpu_irq_request(dpu);
+
+	return 0;
+}
+
+static void sprd_dpu_unbind(struct device *dev, struct device *master,
+	void *data)
+{
+	struct sprd_dpu *dpu = dev_get_drvdata(dev);
+
+	DRM_INFO("%s()\n", __func__);
+
+	drm_crtc_cleanup(&dpu->crtc);
+}
+
+static const struct component_ops dpu_component_ops = {
+	.bind = sprd_dpu_bind,
+	.unbind = sprd_dpu_unbind,
+};
+
+static int sprd_dpu_context_init(struct sprd_dpu *dpu,
+				struct device_node *np)
+{
+	struct resource r;
+	struct dpu_context *ctx = &dpu->ctx;
+
+	if (of_address_to_resource(np, 0, &r)) {
+		DRM_ERROR("parse dt base address failed\n");
+		return -ENODEV;
+	}
+	ctx->base = (unsigned long)ioremap(r.start,
+					resource_size(&r));
+	if (ctx->base == 0) {
+		DRM_ERROR("ioremap base address failed\n");
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static const struct sprd_dpu_ops sharkl3_dpu = {
+	.core = &sharkl3_dpu_core_ops,
+};
+
+static const struct of_device_id dpu_match_table[] = {
+	{ .compatible = "sprd,sharkl3-dpu",
+	  .data = &sharkl3_dpu },
+	{ /* sentinel */ },
+};
+
+static int sprd_dpu_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	const struct of_device_id *of_id =
+		of_match_node(dpu_match_table, np);
+	const struct sprd_dpu_ops *pdata;
+	struct sprd_dpu *dpu;
+	int ret;
+
+	dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
+	if (!dpu)
+		return -ENOMEM;
+
+	pdata = of_device_get_match_data(&pdev->dev);
+	if (pdata) {
+		dpu->core = pdata->core;
+		dpu->ctx.version = "dpu-r2p0";
+	} else {
+		DRM_ERROR("Can't get %s ops data\n", of_id->name);
+		return -EINVAL;
+	}
+
+	ret = sprd_dpu_context_init(dpu, np);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, dpu);
+
+	return component_add(&pdev->dev, &dpu_component_ops);
+}
+
+static int sprd_dpu_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dpu_component_ops);
+	return 0;
+}
+
+struct platform_driver sprd_dpu_driver = {
+	.probe = sprd_dpu_probe,
+	.remove = sprd_dpu_remove,
+	.driver = {
+		.name = "sprd-dpu-drv",
+		.of_match_table = dpu_match_table,
+	},
+};
+
+MODULE_AUTHOR("Leon He <leon.he@unisoc.com>");
+MODULE_AUTHOR("Kevin Tang <kevin.tang@unisoc.com>");
+MODULE_DESCRIPTION("Unisoc Display Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/sprd/sprd_dpu.h b/drivers/gpu/drm/sprd/sprd_dpu.h
new file mode 100644
index 0000000..3e7f91f
--- /dev/null
+++ b/drivers/gpu/drm/sprd/sprd_dpu.h
@@ -0,0 +1,127 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Unisoc Inc.
+ */
+
+#ifndef __SPRD_DPU_H__
+#define __SPRD_DPU_H__
+
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/string.h>
+#include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+#include <uapi/drm/drm_mode.h>
+#include "disp_lib.h"
+
+#define DISPC_INT_DONE_MASK		BIT(0)
+#define DISPC_INT_TE_MASK		BIT(1)
+#define DISPC_INT_ERR_MASK		BIT(2)
+#define DISPC_INT_EDPI_TE_MASK		BIT(3)
+#define DISPC_INT_UPDATE_DONE_MASK	BIT(4)
+#define DISPC_INT_DPI_VSYNC_MASK	BIT(5)
+#define DISPC_INT_WB_DONE_MASK		BIT(6)
+#define DISPC_INT_WB_FAIL_MASK		BIT(7)
+
+/* NOTE: this mask is not a realy dpu interrupt mask */
+#define DISPC_INT_FENCE_SIGNAL_REQUEST	BIT(31)
+
+enum {
+	SPRD_DISPC_IF_DBI = 0,
+	SPRD_DISPC_IF_DPI,
+	SPRD_DISPC_IF_EDPI,
+	SPRD_DISPC_IF_LIMIT
+};
+
+enum {
+	SPRD_IMG_DATA_ENDIAN_B0B1B2B3 = 0,
+	SPRD_IMG_DATA_ENDIAN_B3B2B1B0,
+	SPRD_IMG_DATA_ENDIAN_B2B3B0B1,
+	SPRD_IMG_DATA_ENDIAN_B1B0B3B2,
+	SPRD_IMG_DATA_ENDIAN_LIMIT
+};
+
+struct sprd_dpu_layer {
+	u8 index;
+	u8 planes;
+	u32 addr[4];
+	u32 pitch[4];
+	s16 src_x;
+	s16 src_y;
+	s16 src_w;
+	s16 src_h;
+	s16 dst_x;
+	s16 dst_y;
+	u16 dst_w;
+	u16 dst_h;
+	u32 format;
+	u32 alpha;
+	u32 blending;
+	u32 rotation;
+};
+
+struct dpu_capability {
+	u32 max_layers;
+	const u32 *fmts_ptr;
+	u32 fmts_cnt;
+};
+
+struct dpu_context;
+
+struct dpu_core_ops {
+	int (*init)(struct dpu_context *ctx);
+	void (*fini)(struct dpu_context *ctx);
+	void (*run)(struct dpu_context *ctx);
+	void (*stop)(struct dpu_context *ctx);
+	void (*disable_vsync)(struct dpu_context *ctx);
+	void (*enable_vsync)(struct dpu_context *ctx);
+	u32 (*isr)(struct dpu_context *ctx);
+	void (*ifconfig)(struct dpu_context *ctx);
+	void (*flip)(struct dpu_context *ctx,
+		     struct sprd_dpu_layer layers[], u8 count);
+	int (*capability)(struct dpu_context *ctx,
+			struct dpu_capability *cap);
+	void (*bg_color)(struct dpu_context *ctx, u32 color);
+};
+
+struct sprd_dpu_ops {
+	const struct dpu_core_ops *core;
+};
+
+struct dpu_context {
+	unsigned long base;
+	const char *version;
+	int irq;
+	u8 if_type;
+	struct videomode vm;
+	bool stopped;
+	wait_queue_head_t wait_queue;
+	bool evt_update;
+	bool evt_stop;
+};
+
+struct sprd_dpu {
+	struct device dev;
+	struct drm_crtc crtc;
+	struct dpu_context ctx;
+	const struct dpu_core_ops *core;
+	struct drm_display_mode *mode;
+	struct sprd_dpu_layer *layers;
+	u8 pending_planes;
+};
+
+static inline struct sprd_dpu *crtc_to_dpu(struct drm_crtc *crtc)
+{
+	return crtc ? container_of(crtc, struct sprd_dpu, crtc) : NULL;
+}
+
+extern const struct dpu_core_ops sharkl3_dpu_core_ops;
+
+#endif
diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c
index 4706185..200020f 100644
--- a/drivers/gpu/drm/sprd/sprd_drm.c
+++ b/drivers/gpu/drm/sprd/sprd_drm.c
@@ -200,6 +200,7 @@  static struct platform_driver sprd_drm_driver = {
 
 static struct platform_driver *sprd_drm_drivers[]  = {
 	&sprd_drm_driver,
+	&sprd_dpu_driver,
 };
 
 static int __init sprd_drm_init(void)
diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h
index edf0881..3c32f3a 100644
--- a/drivers/gpu/drm/sprd/sprd_drm.h
+++ b/drivers/gpu/drm/sprd/sprd_drm.h
@@ -13,4 +13,6 @@  struct sprd_drm {
 	struct drm_device *drm;
 };
 
+extern struct platform_driver sprd_dpu_driver;
+
 #endif /* _SPRD_DRM_H_ */