// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2020 Unisoc Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_state *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_state *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_state *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_state *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_state *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_state *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; struct resource *res; int ret; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "failed to get I/O resource\n"); return -EINVAL; } ctx->base = devm_ioremap(dev, res->start, resource_size(res)); if (!ctx->base) { dev_err(dev, "failed to map dpu registers\n"); return -EFAULT; } 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 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 "); MODULE_AUTHOR("Kevin Tang "); MODULE_DESCRIPTION("Unisoc Display Controller Driver"); MODULE_LICENSE("GPL v2");