| /* |
| * drivers/amlogic/drm/meson_plane.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| */ |
| |
| #include "meson_plane.h" |
| #include "meson_crtc.h" |
| #include "meson_vpu.h" |
| #include "meson_drv.h" |
| #include "meson_vpu_pipeline.h" |
| |
| static const u32 supported_drm_formats[] = { |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_XBGR8888, |
| DRM_FORMAT_RGBX8888, |
| DRM_FORMAT_BGRX8888, |
| DRM_FORMAT_ARGB8888, |
| DRM_FORMAT_ABGR8888, |
| DRM_FORMAT_RGBA8888, |
| DRM_FORMAT_BGRA8888, |
| DRM_FORMAT_RGB888, |
| DRM_FORMAT_RGB565, |
| DRM_FORMAT_ARGB1555, |
| DRM_FORMAT_ARGB4444, |
| }; |
| |
| static u64 afbc_gxm_modifier[] = { |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | |
| AFBC_FORMAT_MOD_YTR | |
| AFBC_FORMAT_MOD_SPARSE), |
| /* SPLIT mandates SPARSE, RGB modes mandates YTR */ |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | |
| AFBC_FORMAT_MOD_YTR | |
| AFBC_FORMAT_MOD_SPARSE | |
| AFBC_FORMAT_MOD_SPLIT), |
| DRM_FORMAT_MOD_LINEAR, |
| DRM_FORMAT_MOD_INVALID |
| }; |
| |
| static u64 afbc_g12a_modifier[] = { |
| /* |
| * - TOFIX Support AFBC modifiers for YUV formats (16x16 + TILED) |
| * - SPLIT is mandatory for performances reasons when in 16x16 |
| * block size |
| * - 32x8 block size + SPLIT is mandatory with 4K frame size |
| * for performances reasons |
| */ |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | |
| AFBC_FORMAT_MOD_SPARSE | |
| AFBC_FORMAT_MOD_SPLIT), |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | |
| AFBC_FORMAT_MOD_YTR | |
| AFBC_FORMAT_MOD_SPARSE | |
| AFBC_FORMAT_MOD_SPLIT), |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | |
| AFBC_FORMAT_MOD_SPARSE | |
| AFBC_FORMAT_MOD_SPLIT), |
| DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | |
| AFBC_FORMAT_MOD_YTR | |
| AFBC_FORMAT_MOD_SPARSE | |
| AFBC_FORMAT_MOD_SPLIT), |
| DRM_FORMAT_MOD_LINEAR, |
| DRM_FORMAT_MOD_INVALID |
| }; |
| |
| static void |
| meson_plane_position_calc(struct meson_vpu_osd_layer_info *plane_info, |
| struct drm_plane_state *state, |
| struct drm_display_mode *disp_mode) |
| { |
| u32 dst_w, dst_h, src_w, src_h, scan_mode_out; |
| struct drm_display_mode *mode; |
| |
| if (IS_ERR_OR_NULL(state->crtc)) |
| mode = disp_mode; |
| else |
| mode = &state->crtc->mode; |
| |
| scan_mode_out = mode->flags & DRM_MODE_FLAG_INTERLACE; |
| plane_info->src_x = state->src_x; |
| plane_info->src_y = state->src_y; |
| plane_info->src_w = (state->src_w >> 16) & 0xffff; |
| plane_info->src_h = (state->src_h >> 16) & 0xffff; |
| |
| plane_info->dst_x = state->crtc_x; |
| plane_info->dst_y = state->crtc_y; |
| plane_info->dst_w = state->crtc_w; |
| plane_info->dst_h = state->crtc_h; |
| if (scan_mode_out) { |
| plane_info->dst_y >>= 1; |
| plane_info->dst_h >>= 1; |
| } |
| /*negative position process*/ |
| if (state->crtc_x < 0) { |
| dst_w = state->crtc_w + state->crtc_x; |
| if (dst_w > 0) { |
| src_w = plane_info->src_w * dst_w / state->crtc_w; |
| plane_info->src_x = plane_info->src_w - src_w; |
| plane_info->src_w = src_w; |
| plane_info->dst_w = dst_w; |
| plane_info->dst_x = 0; |
| } else { |
| plane_info->enable = 0; |
| } |
| } |
| if (state->crtc_y < 0) { |
| dst_h = state->crtc_h + state->crtc_y; |
| if (dst_h > 0) { |
| src_h = plane_info->src_h * dst_h / state->crtc_h; |
| plane_info->src_y = plane_info->src_h - src_h; |
| plane_info->src_h = src_h; |
| plane_info->dst_h = dst_h; |
| plane_info->dst_y = 0; |
| } else { |
| plane_info->enable = 0; |
| } |
| } |
| /*overdisplay process*/ |
| if ((plane_info->dst_x + plane_info->dst_w) > mode->hdisplay) { |
| if (plane_info->dst_x >= mode->hdisplay) |
| plane_info->enable = 0; |
| else |
| plane_info->dst_w = |
| mode->hdisplay - plane_info->dst_x; |
| } |
| if ((plane_info->dst_y + plane_info->dst_h) > mode->vdisplay) { |
| if (plane_info->dst_y >= mode->vdisplay) |
| plane_info->enable = 0; |
| else |
| plane_info->dst_h = mode->vdisplay - plane_info->dst_y; |
| } |
| } |
| |
| static int |
| meson_plane_check_size_range(struct meson_vpu_osd_layer_info *plane_info) |
| { |
| u32 dst_w, dst_h, src_w, src_h, ratio_x, ratio_y; |
| int ret; |
| |
| src_w = plane_info->src_w; |
| src_h = plane_info->src_h; |
| dst_w = plane_info->dst_w; |
| dst_h = plane_info->dst_h; |
| ratio_x = 0; |
| ratio_y = 0; |
| ret = 0; |
| |
| if (src_w > dst_w) |
| ratio_x = (src_w + dst_w - 1) / dst_w; |
| if (src_h > dst_h) |
| ratio_y = (src_h + dst_h - 1) / dst_h; |
| if (ratio_x > MESON_OSD_SCLAE_DOWN_LIMIT || |
| ratio_y > MESON_OSD_SCLAE_DOWN_LIMIT) |
| ret = -EDOM; |
| if (src_w < dst_w) |
| ratio_x = (dst_w + src_w - 1) / src_w; |
| if (src_h < dst_h) |
| ratio_y = (dst_h + src_h - 1) / src_h; |
| if (ratio_x > MESON_OSD_SCLAE_UP_LIMIT || |
| ratio_y > MESON_OSD_SCLAE_UP_LIMIT) |
| ret = -EDOM; |
| return ret; |
| } |
| |
| static int meson_plane_fb_check(struct drm_plane *plane, |
| struct drm_plane_state *new_state, |
| struct meson_vpu_osd_layer_info *plane_info) |
| { |
| struct drm_framebuffer *fb = new_state->fb; |
| #ifdef CONFIG_DRM_MESON_USE_ION |
| struct am_osd_plane *osd_plane = to_am_osd_plane(plane); |
| struct meson_drm *drv = osd_plane->drv; |
| struct am_meson_fb *meson_fb; |
| #else |
| struct drm_gem_cma_object *gem; |
| #endif |
| phys_addr_t phyaddr; |
| |
| #ifdef CONFIG_DRM_MESON_USE_ION |
| meson_fb = container_of(fb, struct am_meson_fb, base); |
| if (!meson_fb) { |
| DRM_INFO("meson_fb is NULL!\n"); |
| return -EINVAL; |
| } |
| DRM_DEBUG("meson_fb[id:%d,ref:%d]=0x%p\n", |
| meson_fb->base.base.id, |
| atomic_read(&meson_fb->base.base.refcount.refcount), |
| meson_fb); |
| if (meson_fb->logo && meson_fb->logo->alloc_flag && |
| meson_fb->logo->start) { |
| phyaddr = meson_fb->logo->start; |
| DRM_DEBUG("logo->phyaddr=0x%pa\n", &phyaddr); |
| } else if (meson_fb->bufp) { |
| phyaddr = am_meson_gem_object_get_phyaddr(drv, meson_fb->bufp); |
| } else { |
| phyaddr = 0; |
| DRM_INFO("don't find phyaddr!\n"); |
| return -EINVAL; |
| } |
| #else |
| if (!fb) { |
| DRM_INFO("fb is NULL!\n"); |
| return -EINVAL; |
| } |
| /* Update Canvas with buffer address */ |
| gem = drm_fb_cma_get_gem_obj(fb, 0); |
| if (!gem) { |
| DRM_INFO("gem is NULL!\n"); |
| return -EINVAL; |
| } |
| phyaddr = gem->paddr; |
| #endif |
| plane_info->phy_addr = phyaddr; |
| return 0; |
| } |
| |
| static int meson_plane_get_fb_info(struct drm_plane *plane, |
| struct drm_plane_state *new_state, |
| struct meson_vpu_osd_layer_info *plane_info) |
| { |
| struct am_osd_plane *osd_plane = to_am_osd_plane(plane); |
| struct drm_framebuffer *fb = new_state->fb; |
| struct meson_drm *drv = osd_plane->drv; |
| |
| if (!drv) { |
| DRM_INFO("%s new_state/meson_drm is NULL!\n", __func__); |
| return -EINVAL; |
| } |
| if (osd_plane->plane_index >= MESON_MAX_OSDS) { |
| DRM_INFO("%s invalid plane_index!\n", __func__); |
| return -EINVAL; |
| } |
| plane_info->pixel_format = fb->pixel_format; |
| plane_info->byte_stride = fb->pitches[0]; |
| plane_info->afbc_en = 0; |
| plane_info->afbc_inter_format = 0; |
| |
| /*setup afbc info*/ |
| if (fb->modifier) { |
| plane_info->afbc_en = 1; |
| plane_info->afbc_inter_format = AFBC_EN; |
| } |
| |
| if (fb->modifier & AFBC_FORMAT_MOD_YTR) |
| plane_info->afbc_inter_format |= YUV_TRANSFORM; |
| |
| if (fb->modifier & AFBC_FORMAT_MOD_SPLIT) |
| plane_info->afbc_inter_format |= BLOCK_SPLIT; |
| |
| if (fb->modifier & AFBC_FORMAT_MOD_TILED) |
| plane_info->afbc_inter_format |= TILED_HEADER_EN; |
| |
| if ((fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == |
| AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) |
| plane_info->afbc_inter_format |= SUPER_BLOCK_ASPECT; |
| |
| DRM_DEBUG("flags:%d pixel_format:%d,modifer=%llu\n", |
| fb->flags, fb->pixel_format, |
| fb->modifier); |
| DRM_DEBUG("plane afbc_en=%u, afbc_inter_format=%x\n", |
| plane_info->afbc_en, plane_info->afbc_inter_format); |
| |
| DRM_DEBUG("phy_addr=0x%x,byte_stride=%d,pixel_format=%d\n", |
| plane_info->phy_addr, plane_info->byte_stride, |
| plane_info->pixel_format); |
| DRM_DEBUG("plane_index %d, size %d.\n", osd_plane->plane_index, |
| plane_info->fb_size); |
| return 0; |
| } |
| |
| static int meson_plane_atomic_get_property(struct drm_plane *plane, |
| const struct drm_plane_state *state, |
| struct drm_property *property, |
| uint64_t *val) |
| { |
| struct am_osd_plane *osd_plane; |
| struct am_meson_plane_state *plane_state; |
| |
| osd_plane = to_am_osd_plane(plane); |
| plane_state = to_am_meson_plane_state(state); |
| if (property == osd_plane->prop_premult_en) { |
| *val = plane_state->premult_en; |
| return 0; |
| } |
| |
| DRM_ERROR("failed to get plane property\n"); |
| return -EINVAL; |
| } |
| |
| static int meson_plane_atomic_set_property(struct drm_plane *plane, |
| struct drm_plane_state *state, |
| struct drm_property *property, |
| uint64_t val) |
| { |
| struct am_osd_plane *osd_plane; |
| struct am_meson_plane_state *plane_state; |
| |
| osd_plane = to_am_osd_plane(plane); |
| plane_state = to_am_meson_plane_state(state); |
| if (property == osd_plane->prop_premult_en) { |
| plane_state->premult_en = val; |
| return 0; |
| } |
| |
| DRM_ERROR("failed to set plane property\n"); |
| return -EINVAL; |
| } |
| |
| static struct drm_plane_state * |
| meson_plane_duplicate_state(struct drm_plane *plane) |
| { |
| struct am_meson_plane_state *meson_plane_state, *old_plane_state; |
| |
| if (WARN_ON(!plane->state)) |
| return NULL; |
| |
| old_plane_state = to_am_meson_plane_state(plane->state); |
| meson_plane_state = kmemdup(old_plane_state, |
| sizeof(*meson_plane_state), GFP_KERNEL); |
| if (!meson_plane_state) |
| return NULL; |
| |
| __drm_atomic_helper_plane_duplicate_state(plane, |
| &meson_plane_state->base); |
| |
| return &meson_plane_state->base; |
| } |
| |
| static void meson_plane_destroy_state(struct drm_plane *plane, |
| struct drm_plane_state *state) |
| { |
| struct am_meson_plane_state *amps; |
| |
| amps = to_am_meson_plane_state(state); |
| __drm_atomic_helper_plane_destroy_state(state); |
| kfree(amps); |
| } |
| |
| /* ARM AFBC Decoder for G12A Family */ |
| |
| /* Amlogic G12A Mali AFBC Decoder supported formats */ |
| static bool meson_g12a_afbcd_pixel_fmt(u64 modifier, uint32_t format) |
| { |
| switch (format) { |
| case DRM_FORMAT_XRGB8888: |
| case DRM_FORMAT_ARGB8888: |
| case DRM_FORMAT_XBGR8888: |
| case DRM_FORMAT_ABGR8888: |
| return true; |
| case DRM_FORMAT_RGB888: |
| if (modifier & AFBC_FORMAT_MOD_SPLIT) |
| return false; |
| return true; |
| case DRM_FORMAT_RGB565: |
| if (modifier & AFBC_FORMAT_MOD_SPLIT) |
| return false; |
| return true; |
| /* TOFIX support mode formats */ |
| default: |
| DRM_DEBUG("unsupported afbc format[%08x]\n", format); |
| return false; |
| } |
| } |
| |
| static bool meson_gxm_afbcd_pixel_fmt(u64 modifier, uint32_t format) |
| { |
| switch (format) { |
| case DRM_FORMAT_ABGR8888: |
| case DRM_FORMAT_XBGR8888: |
| return true; |
| /* TOFIX support mode formats */ |
| default: |
| DRM_DEBUG("unsupported afbc format[%08x]\n", format); |
| return false; |
| } |
| } |
| |
| static bool meson_gxm_afbcd_supported_fmt(u64 modifier, uint32_t format) |
| { |
| if (modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) |
| return false; |
| |
| if (!(modifier & AFBC_FORMAT_MOD_YTR)) |
| return false; |
| |
| return meson_gxm_afbcd_pixel_fmt(modifier, format); |
| } |
| |
| bool am_meson_vpu_check_format_mod(struct drm_plane *plane, |
| u32 format, u64 modifier) |
| { |
| if (modifier == DRM_FORMAT_MOD_INVALID) |
| return false; |
| |
| if (modifier == DRM_FORMAT_MOD_LINEAR) |
| return true; |
| |
| if (osd_meson_dev.afbc_type == MALI_AFBC) |
| return meson_g12a_afbcd_pixel_fmt(modifier, format); |
| else if (osd_meson_dev.afbc_type == MESON_AFBC) |
| return meson_gxm_afbcd_supported_fmt(modifier, format); |
| |
| return false; |
| } |
| |
| static const struct drm_plane_funcs am_osd_plane_funs = { |
| .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 = meson_plane_duplicate_state, |
| .atomic_destroy_state = meson_plane_destroy_state, |
| .atomic_set_property = meson_plane_atomic_set_property, |
| .atomic_get_property = meson_plane_atomic_get_property, |
| .format_mod_supported = am_meson_vpu_check_format_mod, |
| }; |
| |
| static int meson_plane_prepare_fb(struct drm_plane *plane, |
| struct drm_plane_state *new_state) |
| { |
| return 0; |
| } |
| |
| static void meson_plane_cleanup_fb(struct drm_plane *plane, |
| struct drm_plane_state *old_state) |
| { |
| struct am_osd_plane *osd_plane = to_am_osd_plane(plane); |
| |
| DRM_DEBUG("osd %d.\n", osd_plane->plane_index); |
| } |
| |
| static void meson_plane_atomic_update(struct drm_plane *plane, |
| struct drm_plane_state *old_state) |
| { |
| |
| DRM_DEBUG("plane atomic_update.\n"); |
| } |
| |
| static int meson_plane_atomic_check(struct drm_plane *plane, |
| struct drm_plane_state *state) |
| { |
| struct meson_vpu_osd_layer_info *plane_info; |
| struct meson_vpu_pipeline_state *mvps; |
| struct am_osd_plane *osd_plane = to_am_osd_plane(plane); |
| struct meson_drm *drv = osd_plane->drv; |
| struct am_meson_plane_state *plane_state; |
| int ret; |
| |
| if (!state || !drv) { |
| DRM_INFO("%s state/meson_drm is NULL!\n", __func__); |
| return -EINVAL; |
| } |
| mvps = meson_vpu_pipeline_get_state(drv->pipeline, state->state); |
| if (!mvps || osd_plane->plane_index >= MESON_MAX_OSDS) { |
| DRM_INFO("%s mvps/osd_plane is NULL!\n", __func__); |
| return -EINVAL; |
| } |
| plane_info = &mvps->plane_info[osd_plane->plane_index]; |
| plane_info->plane_index = osd_plane->plane_index; |
| plane_info->zorder = state->zpos; |
| |
| mvps->plane_index[osd_plane->plane_index] = osd_plane->plane_index; |
| meson_plane_position_calc(plane_info, state, &mvps->pipeline->mode); |
| ret = meson_plane_check_size_range(plane_info); |
| if (ret < 0) { |
| plane_info->enable = 0; |
| DRM_INFO("plane%d size check unsupport!!!\n", |
| plane_info->plane_index); |
| return ret; |
| } |
| ret = meson_plane_fb_check(plane, state, plane_info); |
| if (ret < 0) { |
| plane_info->enable = 0; |
| DRM_DEBUG("plane%d fb is NULL,disable the plane!\n", |
| plane_info->plane_index); |
| return 0; |
| } |
| ret = meson_plane_get_fb_info(plane, state, plane_info); |
| if (ret < 0 || plane_info->src_w > MESON_OSD_INPUT_W_LIMIT || |
| plane_info->src_w == 0) { |
| plane_info->enable = 0; |
| return ret; |
| } |
| |
| plane_state = to_am_meson_plane_state(state); |
| plane_info->premult_en = plane_state->premult_en; |
| plane_info->enable = 1; |
| DRM_DEBUG("index=%d, zorder=%d\n", |
| plane_info->plane_index, plane_info->zorder); |
| DRM_DEBUG("src_x/y/w/h=%d/%d/%d/%d\n", |
| plane_info->src_x, plane_info->src_y, |
| plane_info->src_w, plane_info->src_h); |
| DRM_DEBUG("dst_x/y/w/h=%d/%d/%d/%d\n", |
| plane_info->dst_x, plane_info->dst_y, |
| plane_info->dst_w, plane_info->dst_h); |
| return 0; |
| } |
| |
| static void meson_plane_atomic_disable(struct drm_plane *plane, |
| struct drm_plane_state *old_state) |
| { |
| struct am_osd_plane *osd_plane = to_am_osd_plane(plane); |
| |
| DRM_DEBUG("%s osd %d.\n", __func__, osd_plane->plane_index); |
| } |
| |
| static const struct drm_plane_helper_funcs am_osd_helper_funcs = { |
| .prepare_fb = meson_plane_prepare_fb, |
| .cleanup_fb = meson_plane_cleanup_fb, |
| .atomic_update = meson_plane_atomic_update, |
| .atomic_check = meson_plane_atomic_check, |
| .atomic_disable = meson_plane_atomic_disable, |
| }; |
| |
| int drm_plane_create_premult_en_property(struct drm_plane *plane) |
| { |
| struct drm_device *dev = plane->dev; |
| struct drm_property *prop; |
| struct am_osd_plane *osd_plane; |
| |
| osd_plane = to_am_osd_plane(plane); |
| prop = drm_property_create_bool(dev, DRM_MODE_PROP_ATOMIC, |
| "PREMULT_EN"); |
| if (!prop) |
| return -ENOMEM; |
| |
| drm_object_attach_property(&plane->base, prop, 0); |
| osd_plane->prop_premult_en = prop; |
| |
| return 0; |
| } |
| |
| static struct am_osd_plane *am_plane_create(struct meson_drm *priv, int i) |
| { |
| struct am_osd_plane *osd_plane; |
| struct drm_plane *plane; |
| u32 type = 0; |
| char plane_name[8]; |
| const u64 *format_modifiers = NULL; |
| |
| osd_plane = devm_kzalloc(priv->drm->dev, sizeof(*osd_plane), |
| GFP_KERNEL); |
| if (!osd_plane) |
| return 0; |
| |
| if (osd_meson_dev.afbc_type == MESON_AFBC) |
| format_modifiers = afbc_gxm_modifier; |
| else if (osd_meson_dev.afbc_type == MALI_AFBC) |
| format_modifiers = afbc_g12a_modifier; |
| |
| if (i == 0) |
| type = DRM_PLANE_TYPE_PRIMARY; |
| else |
| type = DRM_PLANE_TYPE_OVERLAY; |
| |
| osd_plane->drv = priv; |
| osd_plane->plane_index = i; |
| |
| plane = &osd_plane->base; |
| sprintf(plane_name, "osd%d", i); |
| |
| drm_universal_plane_init(priv->drm, plane, 0xFF, |
| &am_osd_plane_funs, |
| supported_drm_formats, |
| ARRAY_SIZE(supported_drm_formats), |
| format_modifiers, |
| type, plane_name); |
| |
| drm_plane_create_premult_en_property(plane); |
| drm_plane_helper_add(plane, &am_osd_helper_funcs); |
| osd_drm_debugfs_add(&osd_plane->plane_debugfs_dir, |
| plane_name, osd_plane->plane_index); |
| return osd_plane; |
| } |
| |
| int am_meson_plane_create(struct meson_drm *priv) |
| { |
| struct am_osd_plane *plane; |
| struct meson_vpu_pipeline *pipeline = priv->pipeline; |
| int i, osd_index; |
| |
| |
| for (i = 0; i < pipeline->num_osds; i++) { |
| osd_index = pipeline->osds[i]->base.index; |
| plane = am_plane_create(priv, osd_index); |
| |
| if (!plane) |
| return -ENOMEM; |
| |
| if (i == 0) |
| priv->primary_plane = &plane->base; |
| |
| priv->planes[priv->num_planes++] = plane; |
| } |
| |
| DRM_DEBUG("%s. enter\n", __func__); |
| |
| return 0; |
| } |