| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2020 Unisoc Inc. | |
| */ | |
| #include <linux/component.h> | |
| #include <linux/delay.h> | |
| #include <linux/dma-buf.h> | |
| #include <linux/io.h> | |
| #include <linux/module.h> | |
| #include <linux/of.h> | |
| #include <linux/of_graph.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/wait.h> | |
| #include <linux/workqueue.h> | |
| #include <drm/drm_atomic_helper.h> | |
| #include <drm/drm_blend.h> | |
| #include <drm/drm_fb_dma_helper.h> | |
| #include <drm/drm_framebuffer.h> | |
| #include <drm/drm_gem_dma_helper.h> | |
| #include <drm/drm_gem_framebuffer_helper.h> | |
| #include "sprd_drm.h" | |
| #include "sprd_dpu.h" | |
| #include "sprd_dsi.h" | |
| /* Global control registers */ | |
| #define REG_DPU_CTRL 0x04 | |
| #define REG_DPU_CFG0 0x08 | |
| #define REG_PANEL_SIZE 0x20 | |
| #define REG_BLEND_SIZE 0x24 | |
| #define REG_BG_COLOR 0x2C | |
| /* Layer0 control registers */ | |
| #define REG_LAY_BASE_ADDR0 0x30 | |
| #define REG_LAY_BASE_ADDR1 0x34 | |
| #define REG_LAY_BASE_ADDR2 0x38 | |
| #define REG_LAY_CTRL 0x40 | |
| #define REG_LAY_SIZE 0x44 | |
| #define REG_LAY_PITCH 0x48 | |
| #define REG_LAY_POS 0x4C | |
| #define REG_LAY_ALPHA 0x50 | |
| #define REG_LAY_CROP_START 0x5C | |
| /* Interrupt control registers */ | |
| #define REG_DPU_INT_EN 0x1E0 | |
| #define REG_DPU_INT_CLR 0x1E4 | |
| #define REG_DPU_INT_STS 0x1E8 | |
| /* DPI control registers */ | |
| #define REG_DPI_CTRL 0x1F0 | |
| #define REG_DPI_H_TIMING 0x1F4 | |
| #define REG_DPI_V_TIMING 0x1F8 | |
| /* MMU control registers */ | |
| #define REG_MMU_EN 0x800 | |
| #define REG_MMU_VPN_RANGE 0x80C | |
| #define REG_MMU_PPN1 0x83C | |
| #define REG_MMU_RANGE1 0x840 | |
| #define REG_MMU_PPN2 0x844 | |
| #define REG_MMU_RANGE2 0x848 | |
| /* Global control bits */ | |
| #define BIT_DPU_RUN BIT(0) | |
| #define BIT_DPU_STOP BIT(1) | |
| #define BIT_DPU_REG_UPDATE BIT(2) | |
| #define BIT_DPU_IF_EDPI BIT(0) | |
| /* Layer control bits */ | |
| #define BIT_DPU_LAY_EN BIT(0) | |
| #define BIT_DPU_LAY_LAYER_ALPHA (0x01 << 2) | |
| #define BIT_DPU_LAY_COMBO_ALPHA (0x02 << 2) | |
| #define BIT_DPU_LAY_FORMAT_YUV422_2PLANE (0x00 << 4) | |
| #define BIT_DPU_LAY_FORMAT_YUV420_2PLANE (0x01 << 4) | |
| #define BIT_DPU_LAY_FORMAT_YUV420_3PLANE (0x02 << 4) | |
| #define BIT_DPU_LAY_FORMAT_ARGB8888 (0x03 << 4) | |
| #define BIT_DPU_LAY_FORMAT_RGB565 (0x04 << 4) | |
| #define BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3 (0x00 << 8) | |
| #define BIT_DPU_LAY_DATA_ENDIAN_B3B2B1B0 (0x01 << 8) | |
| #define BIT_DPU_LAY_NO_SWITCH (0x00 << 10) | |
| #define BIT_DPU_LAY_RB_OR_UV_SWITCH (0x01 << 10) | |
| #define BIT_DPU_LAY_MODE_BLEND_NORMAL (0x00 << 16) | |
| #define BIT_DPU_LAY_MODE_BLEND_PREMULT (0x01 << 16) | |
| #define BIT_DPU_LAY_ROTATION_0 (0x00 << 20) | |
| #define BIT_DPU_LAY_ROTATION_90 (0x01 << 20) | |
| #define BIT_DPU_LAY_ROTATION_180 (0x02 << 20) | |
| #define BIT_DPU_LAY_ROTATION_270 (0x03 << 20) | |
| #define BIT_DPU_LAY_ROTATION_0_M (0x04 << 20) | |
| #define BIT_DPU_LAY_ROTATION_90_M (0x05 << 20) | |
| #define BIT_DPU_LAY_ROTATION_180_M (0x06 << 20) | |
| #define BIT_DPU_LAY_ROTATION_270_M (0x07 << 20) | |
| /* Interrupt control & status bits */ | |
| #define BIT_DPU_INT_DONE BIT(0) | |
| #define BIT_DPU_INT_TE BIT(1) | |
| #define BIT_DPU_INT_ERR BIT(2) | |
| #define BIT_DPU_INT_UPDATE_DONE BIT(4) | |
| #define BIT_DPU_INT_VSYNC BIT(5) | |
| /* DPI control bits */ | |
| #define BIT_DPU_EDPI_TE_EN BIT(8) | |
| #define BIT_DPU_EDPI_FROM_EXTERNAL_PAD BIT(10) | |
| #define BIT_DPU_DPI_HALT_EN BIT(16) | |
| static const u32 layer_fmts[] = { | |
| DRM_FORMAT_XRGB8888, | |
| DRM_FORMAT_XBGR8888, | |
| DRM_FORMAT_ARGB8888, | |
| DRM_FORMAT_ABGR8888, | |
| DRM_FORMAT_RGBA8888, | |
| DRM_FORMAT_BGRA8888, | |
| DRM_FORMAT_RGBX8888, | |
| DRM_FORMAT_RGB565, | |
| DRM_FORMAT_BGR565, | |
| DRM_FORMAT_NV12, | |
| DRM_FORMAT_NV21, | |
| DRM_FORMAT_NV16, | |
| DRM_FORMAT_NV61, | |
| DRM_FORMAT_YUV420, | |
| DRM_FORMAT_YVU420, | |
| }; | |
| struct sprd_plane { | |
| struct drm_plane base; | |
| }; | |
| static int dpu_wait_stop_done(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->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) { | |
| drm_err(dpu->drm, "dpu wait for stop done time out!\n"); | |
| return -ETIMEDOUT; | |
| } | |
| return 0; | |
| } | |
| static int dpu_wait_update_done(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| int rc; | |
| ctx->evt_update = false; | |
| rc = wait_event_interruptible_timeout(ctx->wait_queue, ctx->evt_update, | |
| msecs_to_jiffies(500)); | |
| if (!rc) { | |
| drm_err(dpu->drm, "dpu wait for reg update done time out!\n"); | |
| return -ETIMEDOUT; | |
| } | |
| return 0; | |
| } | |
| static u32 drm_format_to_dpu(struct drm_framebuffer *fb) | |
| { | |
| u32 format = 0; | |
| switch (fb->format->format) { | |
| case DRM_FORMAT_BGRA8888: | |
| /* BGRA8888 -> ARGB8888 */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B3B2B1B0; | |
| format |= BIT_DPU_LAY_FORMAT_ARGB8888; | |
| break; | |
| case DRM_FORMAT_RGBX8888: | |
| case DRM_FORMAT_RGBA8888: | |
| /* RGBA8888 -> ABGR8888 */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B3B2B1B0; | |
| fallthrough; | |
| case DRM_FORMAT_ABGR8888: | |
| /* RB switch */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| fallthrough; | |
| case DRM_FORMAT_ARGB8888: | |
| format |= BIT_DPU_LAY_FORMAT_ARGB8888; | |
| break; | |
| case DRM_FORMAT_XBGR8888: | |
| /* RB switch */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| fallthrough; | |
| case DRM_FORMAT_XRGB8888: | |
| format |= BIT_DPU_LAY_FORMAT_ARGB8888; | |
| break; | |
| case DRM_FORMAT_BGR565: | |
| /* RB switch */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| fallthrough; | |
| case DRM_FORMAT_RGB565: | |
| format |= BIT_DPU_LAY_FORMAT_RGB565; | |
| break; | |
| case DRM_FORMAT_NV12: | |
| /* 2-Lane: Yuv420 */ | |
| format |= BIT_DPU_LAY_FORMAT_YUV420_2PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_NO_SWITCH; | |
| break; | |
| case DRM_FORMAT_NV21: | |
| /* 2-Lane: Yuv420 */ | |
| format |= BIT_DPU_LAY_FORMAT_YUV420_2PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| break; | |
| case DRM_FORMAT_NV16: | |
| /* 2-Lane: Yuv422 */ | |
| format |= BIT_DPU_LAY_FORMAT_YUV422_2PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B3B2B1B0; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| break; | |
| case DRM_FORMAT_NV61: | |
| /* 2-Lane: Yuv422 */ | |
| format |= BIT_DPU_LAY_FORMAT_YUV422_2PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_NO_SWITCH; | |
| break; | |
| case DRM_FORMAT_YUV420: | |
| format |= BIT_DPU_LAY_FORMAT_YUV420_3PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_NO_SWITCH; | |
| break; | |
| case DRM_FORMAT_YVU420: | |
| format |= BIT_DPU_LAY_FORMAT_YUV420_3PLANE; | |
| /* Y endian */ | |
| format |= BIT_DPU_LAY_DATA_ENDIAN_B0B1B2B3; | |
| /* UV endian */ | |
| format |= BIT_DPU_LAY_RB_OR_UV_SWITCH; | |
| break; | |
| default: | |
| break; | |
| } | |
| return format; | |
| } | |
| static u32 drm_rotation_to_dpu(struct drm_plane_state *state) | |
| { | |
| u32 rotation = 0; | |
| switch (state->rotation) { | |
| default: | |
| case DRM_MODE_ROTATE_0: | |
| rotation = BIT_DPU_LAY_ROTATION_0; | |
| break; | |
| case DRM_MODE_ROTATE_90: | |
| rotation = BIT_DPU_LAY_ROTATION_90; | |
| break; | |
| case DRM_MODE_ROTATE_180: | |
| rotation = BIT_DPU_LAY_ROTATION_180; | |
| break; | |
| case DRM_MODE_ROTATE_270: | |
| rotation = BIT_DPU_LAY_ROTATION_270; | |
| break; | |
| case DRM_MODE_REFLECT_Y: | |
| rotation = BIT_DPU_LAY_ROTATION_180_M; | |
| break; | |
| case (DRM_MODE_REFLECT_Y | DRM_MODE_ROTATE_90): | |
| rotation = BIT_DPU_LAY_ROTATION_90_M; | |
| break; | |
| case DRM_MODE_REFLECT_X: | |
| rotation = BIT_DPU_LAY_ROTATION_0_M; | |
| break; | |
| case (DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90): | |
| rotation = BIT_DPU_LAY_ROTATION_270_M; | |
| break; | |
| } | |
| return rotation; | |
| } | |
| static u32 drm_blend_to_dpu(struct drm_plane_state *state) | |
| { | |
| u32 blend = 0; | |
| switch (state->pixel_blend_mode) { | |
| case DRM_MODE_BLEND_COVERAGE: | |
| /* alpha mode select - combo alpha */ | |
| blend |= BIT_DPU_LAY_COMBO_ALPHA; | |
| /* Normal mode */ | |
| blend |= BIT_DPU_LAY_MODE_BLEND_NORMAL; | |
| break; | |
| case DRM_MODE_BLEND_PREMULTI: | |
| /* alpha mode select - combo alpha */ | |
| blend |= BIT_DPU_LAY_COMBO_ALPHA; | |
| /* Pre-mult mode */ | |
| blend |= BIT_DPU_LAY_MODE_BLEND_PREMULT; | |
| break; | |
| case DRM_MODE_BLEND_PIXEL_NONE: | |
| default: | |
| /* don't do blending, maybe RGBX */ | |
| /* alpha mode select - layer alpha */ | |
| blend |= BIT_DPU_LAY_LAYER_ALPHA; | |
| break; | |
| } | |
| return blend; | |
| } | |
| static void sprd_dpu_layer(struct sprd_dpu *dpu, struct drm_plane_state *state) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| struct drm_gem_dma_object *dma_obj; | |
| struct drm_framebuffer *fb = state->fb; | |
| u32 addr, size, offset, pitch, blend, format, rotation; | |
| u32 src_x = state->src_x >> 16; | |
| u32 src_y = state->src_y >> 16; | |
| u32 src_w = state->src_w >> 16; | |
| u32 src_h = state->src_h >> 16; | |
| u32 dst_x = state->crtc_x; | |
| u32 dst_y = state->crtc_y; | |
| u32 alpha = state->alpha; | |
| u32 index = state->zpos; | |
| int i; | |
| offset = (dst_x & 0xffff) | (dst_y << 16); | |
| size = (src_w & 0xffff) | (src_h << 16); | |
| for (i = 0; i < fb->format->num_planes; i++) { | |
| dma_obj = drm_fb_dma_get_gem_obj(fb, i); | |
| addr = dma_obj->dma_addr + fb->offsets[i]; | |
| if (i == 0) | |
| layer_reg_wr(ctx, REG_LAY_BASE_ADDR0, addr, index); | |
| else if (i == 1) | |
| layer_reg_wr(ctx, REG_LAY_BASE_ADDR1, addr, index); | |
| else | |
| layer_reg_wr(ctx, REG_LAY_BASE_ADDR2, addr, index); | |
| } | |
| if (fb->format->num_planes == 3) { | |
| /* UV pitch is 1/2 of Y pitch */ | |
| pitch = (fb->pitches[0] / fb->format->cpp[0]) | | |
| (fb->pitches[0] / fb->format->cpp[0] << 15); | |
| } else { | |
| pitch = fb->pitches[0] / fb->format->cpp[0]; | |
| } | |
| layer_reg_wr(ctx, REG_LAY_POS, offset, index); | |
| layer_reg_wr(ctx, REG_LAY_SIZE, size, index); | |
| layer_reg_wr(ctx, REG_LAY_CROP_START, | |
| src_y << 16 | src_x, index); | |
| layer_reg_wr(ctx, REG_LAY_ALPHA, alpha, index); | |
| layer_reg_wr(ctx, REG_LAY_PITCH, pitch, index); | |
| format = drm_format_to_dpu(fb); | |
| blend = drm_blend_to_dpu(state); | |
| rotation = drm_rotation_to_dpu(state); | |
| layer_reg_wr(ctx, REG_LAY_CTRL, BIT_DPU_LAY_EN | | |
| format | | |
| blend | | |
| rotation, | |
| index); | |
| } | |
| static void sprd_dpu_flip(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| /* | |
| * Make sure the dpu is in stop status. DPU 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_DPU_IF_EDPI) | |
| dpu_wait_stop_done(dpu); | |
| /* update trigger and wait */ | |
| if (ctx->if_type == SPRD_DPU_IF_DPI) { | |
| if (!ctx->stopped) { | |
| dpu_reg_set(ctx, REG_DPU_CTRL, BIT_DPU_REG_UPDATE); | |
| dpu_wait_update_done(dpu); | |
| } | |
| dpu_reg_set(ctx, REG_DPU_INT_EN, BIT_DPU_INT_ERR); | |
| } else if (ctx->if_type == SPRD_DPU_IF_EDPI) { | |
| dpu_reg_set(ctx, REG_DPU_CTRL, BIT_DPU_RUN); | |
| ctx->stopped = false; | |
| } | |
| } | |
| static void sprd_dpu_init(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| u32 int_mask = 0; | |
| writel(0x00, ctx->base + REG_BG_COLOR); | |
| writel(0x00, ctx->base + REG_MMU_EN); | |
| writel(0x00, ctx->base + REG_MMU_PPN1); | |
| writel(0xffff, ctx->base + REG_MMU_RANGE1); | |
| writel(0x00, ctx->base + REG_MMU_PPN2); | |
| writel(0xffff, ctx->base + REG_MMU_RANGE2); | |
| writel(0x1ffff, ctx->base + REG_MMU_VPN_RANGE); | |
| if (ctx->if_type == SPRD_DPU_IF_DPI) { | |
| /* use dpi as interface */ | |
| dpu_reg_clr(ctx, REG_DPU_CFG0, BIT_DPU_IF_EDPI); | |
| /* disable Halt function for SPRD DSI */ | |
| dpu_reg_clr(ctx, REG_DPI_CTRL, BIT_DPU_DPI_HALT_EN); | |
| /* select te from external pad */ | |
| dpu_reg_set(ctx, REG_DPI_CTRL, BIT_DPU_EDPI_FROM_EXTERNAL_PAD); | |
| /* enable dpu update done INT */ | |
| int_mask |= BIT_DPU_INT_UPDATE_DONE; | |
| /* enable dpu done INT */ | |
| int_mask |= BIT_DPU_INT_DONE; | |
| /* enable dpu dpi vsync */ | |
| int_mask |= BIT_DPU_INT_VSYNC; | |
| /* enable dpu TE INT */ | |
| int_mask |= BIT_DPU_INT_TE; | |
| /* enable underflow err INT */ | |
| int_mask |= BIT_DPU_INT_ERR; | |
| } else if (ctx->if_type == SPRD_DPU_IF_EDPI) { | |
| /* use edpi as interface */ | |
| dpu_reg_set(ctx, REG_DPU_CFG0, BIT_DPU_IF_EDPI); | |
| /* use external te */ | |
| dpu_reg_set(ctx, REG_DPI_CTRL, BIT_DPU_EDPI_FROM_EXTERNAL_PAD); | |
| /* enable te */ | |
| dpu_reg_set(ctx, REG_DPI_CTRL, BIT_DPU_EDPI_TE_EN); | |
| /* enable stop done INT */ | |
| int_mask |= BIT_DPU_INT_DONE; | |
| /* enable TE INT */ | |
| int_mask |= BIT_DPU_INT_TE; | |
| } | |
| writel(int_mask, ctx->base + REG_DPU_INT_EN); | |
| } | |
| static void sprd_dpu_fini(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| writel(0x00, ctx->base + REG_DPU_INT_EN); | |
| writel(0xff, ctx->base + REG_DPU_INT_CLR); | |
| } | |
| static void sprd_dpi_init(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| u32 reg_val; | |
| u32 size; | |
| size = (ctx->vm.vactive << 16) | ctx->vm.hactive; | |
| writel(size, ctx->base + REG_PANEL_SIZE); | |
| writel(size, ctx->base + REG_BLEND_SIZE); | |
| if (ctx->if_type == SPRD_DPU_IF_DPI) { | |
| /* set dpi timing */ | |
| reg_val = ctx->vm.hsync_len << 0 | | |
| ctx->vm.hback_porch << 8 | | |
| ctx->vm.hfront_porch << 20; | |
| writel(reg_val, ctx->base + REG_DPI_H_TIMING); | |
| reg_val = ctx->vm.vsync_len << 0 | | |
| ctx->vm.vback_porch << 8 | | |
| ctx->vm.vfront_porch << 20; | |
| writel(reg_val, ctx->base + REG_DPI_V_TIMING); | |
| } | |
| } | |
| void sprd_dpu_run(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| dpu_reg_set(ctx, REG_DPU_CTRL, BIT_DPU_RUN); | |
| ctx->stopped = false; | |
| } | |
| void sprd_dpu_stop(struct sprd_dpu *dpu) | |
| { | |
| struct dpu_context *ctx = &dpu->ctx; | |
| if (ctx->if_type == SPRD_DPU_IF_DPI) | |
| dpu_reg_set(ctx, REG_DPU_CTRL, BIT_DPU_STOP); | |
| dpu_wait_stop_done(dpu); | |
| } | |
| static int sprd_plane_atomic_check(struct drm_plane *plane, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, | |
| plane); | |
| struct drm_crtc_state *crtc_state; | |
| u32 fmt; | |
| if (!plane_state->fb || !plane_state->crtc) | |
| return 0; | |
| fmt = drm_format_to_dpu(plane_state->fb); | |
| if (!fmt) | |
| return -EINVAL; | |
| crtc_state = drm_atomic_get_crtc_state(plane_state->state, plane_state->crtc); | |
| if (IS_ERR(crtc_state)) | |
| return PTR_ERR(crtc_state); | |
| return drm_atomic_helper_check_plane_state(plane_state, crtc_state, | |
| DRM_PLANE_NO_SCALING, | |
| DRM_PLANE_NO_SCALING, | |
| true, true); | |
| } | |
| static void sprd_plane_atomic_update(struct drm_plane *drm_plane, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, | |
| drm_plane); | |
| struct sprd_dpu *dpu = to_sprd_crtc(new_state->crtc); | |
| /* start configure dpu layers */ | |
| sprd_dpu_layer(dpu, new_state); | |
| } | |
| static void sprd_plane_atomic_disable(struct drm_plane *drm_plane, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, | |
| drm_plane); | |
| struct sprd_dpu *dpu = to_sprd_crtc(old_state->crtc); | |
| layer_reg_wr(&dpu->ctx, REG_LAY_CTRL, 0x00, old_state->zpos); | |
| } | |
| static void sprd_plane_create_properties(struct sprd_plane *plane, 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(&plane->base, | |
| DRM_MODE_ROTATE_0, | |
| DRM_MODE_ROTATE_MASK | | |
| DRM_MODE_REFLECT_MASK); | |
| /* create alpha property */ | |
| drm_plane_create_alpha_property(&plane->base); | |
| /* create blend mode property */ | |
| drm_plane_create_blend_mode_property(&plane->base, supported_modes); | |
| /* create zpos property */ | |
| drm_plane_create_zpos_immutable_property(&plane->base, index); | |
| } | |
| 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 sprd_plane *sprd_planes_init(struct drm_device *drm) | |
| { | |
| struct sprd_plane *plane, *primary; | |
| enum drm_plane_type plane_type; | |
| int i; | |
| for (i = 0; i < 6; i++) { | |
| plane_type = (i == 0) ? DRM_PLANE_TYPE_PRIMARY : | |
| DRM_PLANE_TYPE_OVERLAY; | |
| plane = drmm_universal_plane_alloc(drm, struct sprd_plane, base, | |
| 1, &sprd_plane_funcs, | |
| layer_fmts, ARRAY_SIZE(layer_fmts), | |
| NULL, plane_type, NULL); | |
| if (IS_ERR(plane)) { | |
| drm_err(drm, "failed to init drm plane: %d\n", i); | |
| return plane; | |
| } | |
| drm_plane_helper_add(&plane->base, &sprd_plane_helper_funcs); | |
| sprd_plane_create_properties(plane, i); | |
| if (i == 0) | |
| primary = plane; | |
| } | |
| return primary; | |
| } | |
| static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| struct drm_display_mode *mode = &crtc->state->adjusted_mode; | |
| struct drm_encoder *encoder; | |
| struct sprd_dsi *dsi; | |
| drm_display_mode_to_videomode(mode, &dpu->ctx.vm); | |
| drm_for_each_encoder_mask(encoder, crtc->dev, | |
| crtc->state->encoder_mask) { | |
| dsi = encoder_to_dsi(encoder); | |
| if (dsi->slave->mode_flags & MIPI_DSI_MODE_VIDEO) | |
| dpu->ctx.if_type = SPRD_DPU_IF_DPI; | |
| else | |
| dpu->ctx.if_type = SPRD_DPU_IF_EDPI; | |
| } | |
| sprd_dpi_init(dpu); | |
| } | |
| static void sprd_crtc_atomic_enable(struct drm_crtc *crtc, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| sprd_dpu_init(dpu); | |
| drm_crtc_vblank_on(&dpu->base); | |
| } | |
| static void sprd_crtc_atomic_disable(struct drm_crtc *crtc, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| struct drm_device *drm = dpu->base.dev; | |
| drm_crtc_vblank_off(&dpu->base); | |
| 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 void sprd_crtc_atomic_flush(struct drm_crtc *crtc, | |
| struct drm_atomic_commit *state) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| struct drm_device *drm = dpu->base.dev; | |
| sprd_dpu_flip(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_enable_vblank(struct drm_crtc *crtc) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| dpu_reg_set(&dpu->ctx, REG_DPU_INT_EN, BIT_DPU_INT_VSYNC); | |
| return 0; | |
| } | |
| static void sprd_crtc_disable_vblank(struct drm_crtc *crtc) | |
| { | |
| struct sprd_dpu *dpu = to_sprd_crtc(crtc); | |
| dpu_reg_clr(&dpu->ctx, REG_DPU_INT_EN, BIT_DPU_INT_VSYNC); | |
| } | |
| static const struct drm_crtc_helper_funcs sprd_crtc_helper_funcs = { | |
| .mode_set_nofb = sprd_crtc_mode_set_nofb, | |
| .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 struct sprd_dpu *sprd_crtc_init(struct drm_device *drm, | |
| struct drm_plane *primary, struct device *dev) | |
| { | |
| struct device_node *port; | |
| struct sprd_dpu *dpu; | |
| dpu = drmm_crtc_alloc_with_planes(drm, struct sprd_dpu, base, | |
| primary, NULL, | |
| &sprd_crtc_funcs, NULL); | |
| if (IS_ERR(dpu)) { | |
| drm_err(drm, "failed to init crtc\n"); | |
| return dpu; | |
| } | |
| drm_crtc_helper_add(&dpu->base, &sprd_crtc_helper_funcs); | |
| /* | |
| * set crtc port so that drm_of_find_possible_crtcs call works | |
| */ | |
| port = of_graph_get_port_by_id(dev->of_node, 0); | |
| if (!port) { | |
| drm_err(drm, "failed to found crtc output port for %s\n", | |
| dev->of_node->full_name); | |
| return ERR_PTR(-EINVAL); | |
| } | |
| dpu->base.port = port; | |
| of_node_put(port); | |
| return dpu; | |
| } | |
| static irqreturn_t sprd_dpu_isr(int irq, void *data) | |
| { | |
| struct sprd_dpu *dpu = data; | |
| struct dpu_context *ctx = &dpu->ctx; | |
| u32 reg_val, int_mask = 0; | |
| reg_val = readl(ctx->base + REG_DPU_INT_STS); | |
| /* disable err interrupt */ | |
| if (reg_val & BIT_DPU_INT_ERR) { | |
| int_mask |= BIT_DPU_INT_ERR; | |
| drm_warn(dpu->drm, "Warning: dpu underflow!\n"); | |
| } | |
| /* dpu update done isr */ | |
| if (reg_val & BIT_DPU_INT_UPDATE_DONE) { | |
| ctx->evt_update = true; | |
| wake_up_interruptible_all(&ctx->wait_queue); | |
| } | |
| /* dpu stop done isr */ | |
| if (reg_val & BIT_DPU_INT_DONE) { | |
| ctx->evt_stop = true; | |
| wake_up_interruptible_all(&ctx->wait_queue); | |
| } | |
| if (reg_val & BIT_DPU_INT_VSYNC) | |
| drm_crtc_handle_vblank(&dpu->base); | |
| writel(reg_val, ctx->base + REG_DPU_INT_CLR); | |
| dpu_reg_clr(ctx, REG_DPU_INT_EN, int_mask); | |
| return IRQ_HANDLED; | |
| } | |
| static int sprd_dpu_context_init(struct sprd_dpu *dpu, | |
| struct device *dev) | |
| { | |
| struct platform_device *pdev = to_platform_device(dev); | |
| struct dpu_context *ctx = &dpu->ctx; | |
| int ret; | |
| ctx->base = devm_platform_ioremap_resource(pdev, 0); | |
| if (IS_ERR(ctx->base)) { | |
| dev_err(dev, "failed to map dpu registers\n"); | |
| return PTR_ERR(ctx->base); | |
| } | |
| ctx->irq = platform_get_irq(pdev, 0); | |
| if (ctx->irq < 0) | |
| return ctx->irq; | |
| /* disable and clear interrupts before register dpu IRQ. */ | |
| writel(0x00, ctx->base + REG_DPU_INT_EN); | |
| writel(0xff, ctx->base + REG_DPU_INT_CLR); | |
| ret = devm_request_irq(dev, ctx->irq, sprd_dpu_isr, | |
| IRQF_TRIGGER_NONE, "DPU", dpu); | |
| if (ret) { | |
| dev_err(dev, "failed to register dpu irq handler\n"); | |
| return ret; | |
| } | |
| init_waitqueue_head(&ctx->wait_queue); | |
| return 0; | |
| } | |
| static int sprd_dpu_bind(struct device *dev, struct device *master, void *data) | |
| { | |
| struct drm_device *drm = data; | |
| struct sprd_dpu *dpu; | |
| struct sprd_plane *plane; | |
| int ret; | |
| plane = sprd_planes_init(drm); | |
| if (IS_ERR(plane)) | |
| return PTR_ERR(plane); | |
| dpu = sprd_crtc_init(drm, &plane->base, dev); | |
| if (IS_ERR(dpu)) | |
| return PTR_ERR(dpu); | |
| dpu->drm = drm; | |
| dev_set_drvdata(dev, dpu); | |
| ret = sprd_dpu_context_init(dpu, dev); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| static const struct component_ops dpu_component_ops = { | |
| .bind = sprd_dpu_bind, | |
| }; | |
| static const struct of_device_id dpu_match_table[] = { | |
| { .compatible = "sprd,sharkl3-dpu" }, | |
| { /* sentinel */ }, | |
| }; | |
| MODULE_DEVICE_TABLE(of, dpu_match_table); | |
| static int sprd_dpu_probe(struct platform_device *pdev) | |
| { | |
| return component_add(&pdev->dev, &dpu_component_ops); | |
| } | |
| static void sprd_dpu_remove(struct platform_device *pdev) | |
| { | |
| component_del(&pdev->dev, &dpu_component_ops); | |
| } | |
| 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"); |