| /* |
| * Copyright 2018 NXP |
| * |
| * 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 <linux/module.h> |
| #include <drm/drmP.h> |
| #include <drm/drm_atomic.h> |
| #include <drm/drm_atomic_helper.h> |
| #include <drm/drm_fb_cma_helper.h> |
| #include <drm/drm_framebuffer.h> |
| #include <drm/drm_gem_cma_helper.h> |
| #include <drm/drm_plane.h> |
| #include <drm/drm_plane_helper.h> |
| #include <drm/drm_rect.h> |
| #include <video/imx-lcdif.h> |
| |
| #include "lcdif-plane.h" |
| |
| static uint32_t lcdif_pixel_formats[] = { |
| DRM_FORMAT_XRGB8888, |
| DRM_FORMAT_ARGB8888, |
| DRM_FORMAT_RGB565, |
| DRM_FORMAT_XBGR8888, |
| DRM_FORMAT_ABGR8888, |
| DRM_FORMAT_RGBX8888, |
| DRM_FORMAT_RGBA8888, |
| DRM_FORMAT_ARGB1555, |
| DRM_FORMAT_XRGB1555, |
| DRM_FORMAT_ABGR1555, |
| DRM_FORMAT_XBGR1555, |
| DRM_FORMAT_BGR565, |
| }; |
| |
| static int lcdif_plane_atomic_check(struct drm_plane *plane, |
| struct drm_plane_state *plane_state) |
| { |
| int ret; |
| struct drm_plane_state *old_state = plane->state; |
| struct drm_framebuffer *fb = plane_state->fb; |
| struct drm_framebuffer *old_fb = old_state->fb; |
| struct drm_crtc_state *crtc_state; |
| struct drm_display_mode *mode; |
| struct drm_rect clip = { 0 }; |
| |
| /* 'fb' should also be NULL which has been checked in |
| * the core sanity check function 'drm_atomic_plane_check()' |
| */ |
| if (!plane_state->crtc) { |
| WARN_ON(fb); |
| return 0; |
| } |
| |
| /* lcdif crtc can only display from (0,0) for each plane */ |
| if (plane_state->crtc_x || plane_state->crtc_y) |
| return -EINVAL; |
| |
| crtc_state = drm_atomic_get_existing_crtc_state(plane_state->state, |
| plane_state->crtc); |
| mode = &crtc_state->adjusted_mode; |
| |
| clip.x2 = mode->hdisplay; |
| clip.y2 = mode->vdisplay; |
| |
| ret = drm_plane_helper_check_state(plane_state, &clip, |
| DRM_PLANE_HELPER_NO_SCALING, |
| DRM_PLANE_HELPER_NO_SCALING, |
| false, true); |
| if (ret) |
| return ret; |
| |
| if (!plane_state->visible) |
| return -EINVAL; |
| |
| /* force 'mode_changed' when fb pitches changed, since |
| * the pitch related registers configuration of LCDIF |
| * can not be done when LCDIF is running. |
| */ |
| if (old_fb && likely(!crtc_state->mode_changed)) { |
| if (old_fb->pitches[0] != fb->pitches[0]) |
| crtc_state->mode_changed = true; |
| } |
| |
| return 0; |
| } |
| |
| static void lcdif_plane_atomic_update(struct drm_plane *plane, |
| struct drm_plane_state *old_state) |
| { |
| struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane); |
| struct lcdif_soc *lcdif = lcdif_plane->lcdif; |
| struct drm_plane_state *state = plane->state; |
| struct drm_framebuffer *fb = state->fb; |
| struct drm_gem_cma_object *gem_obj = NULL; |
| u32 fb_addr, src_off, src_w, fb_idx, cpp, stride; |
| bool crop; |
| |
| /* plane and crtc is disabling */ |
| if (!fb) |
| return; |
| |
| /* TODO: for now we just update the next buf addr |
| * and the fb pixel format, since the mode set will |
| * be done in crtc's ->enable() helper func |
| */ |
| if (plane->type == DRM_PLANE_TYPE_PRIMARY) |
| lcdif_set_pix_fmt(lcdif, fb->format->format); |
| |
| switch (plane->type) { |
| case DRM_PLANE_TYPE_PRIMARY: |
| /* TODO: only support RGB */ |
| gem_obj = drm_fb_cma_get_gem_obj(fb, 0); |
| src_off = (state->src_y >> 16) * fb->pitches[0] + |
| (state->src_x >> 16) * fb->format->cpp[0]; |
| fb_addr = gem_obj->paddr + fb->offsets[0] + src_off; |
| fb_idx = 0; |
| break; |
| default: |
| /* TODO: add overlay later */ |
| return; |
| } |
| |
| lcdif_set_fb_addr(lcdif, fb_idx, fb_addr); |
| |
| /* config horizontal cropping if crtc needs modeset */ |
| if (unlikely(drm_atomic_crtc_needs_modeset(state->crtc->state))) { |
| cpp = fb->format->cpp[0]; |
| stride = DIV_ROUND_UP(fb->pitches[0], cpp); |
| |
| src_w = state->src_w >> 16; |
| WARN_ON(src_w > fb->width); |
| |
| crop = src_w != stride ? true : false; |
| lcdif_set_fb_hcrop(lcdif, src_w, stride, crop); |
| } |
| |
| lcdif_enable_controller(lcdif); |
| } |
| |
| static void lcdif_plane_atomic_disable(struct drm_plane *plane, |
| struct drm_plane_state *old_state) |
| { |
| struct drm_plane_state *state = plane->state; |
| struct drm_framebuffer *fb = state->fb; |
| |
| WARN_ON(fb); |
| |
| /* TODO: CRTC disabled has been done by CRTC helper function, |
| * so it seems that no more required, the only possible thing |
| * is to set next buf addr to 0 in CRTC |
| */ |
| } |
| |
| static const struct drm_plane_helper_funcs lcdif_plane_helper_funcs = { |
| .atomic_check = lcdif_plane_atomic_check, |
| .atomic_update = lcdif_plane_atomic_update, |
| .atomic_disable = lcdif_plane_atomic_disable, |
| }; |
| |
| static void lcdif_plane_destroy(struct drm_plane *plane) |
| { |
| struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane); |
| |
| drm_plane_cleanup(plane); |
| kfree(lcdif_plane); |
| } |
| |
| static const struct drm_plane_funcs lcdif_plane_funcs = { |
| .update_plane = drm_atomic_helper_update_plane, |
| .disable_plane = drm_atomic_helper_disable_plane, |
| .destroy = lcdif_plane_destroy, |
| .reset = drm_atomic_helper_plane_reset, |
| .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, |
| .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, |
| }; |
| |
| struct lcdif_plane *lcdif_plane_init(struct drm_device *dev, |
| struct lcdif_soc *lcdif, |
| unsigned int possible_crtcs, |
| enum drm_plane_type type, |
| unsigned int zpos) |
| { |
| int ret; |
| struct lcdif_plane *lcdif_plane; |
| |
| /* lcdif doesn't support fb modifiers */ |
| if (zpos || dev->mode_config.allow_fb_modifiers) |
| return ERR_PTR(-EINVAL); |
| |
| lcdif_plane = kzalloc(sizeof(*lcdif_plane), GFP_KERNEL); |
| if (!lcdif_plane) |
| return ERR_PTR(-ENOMEM); |
| |
| lcdif_plane->lcdif = lcdif; |
| |
| drm_plane_helper_add(&lcdif_plane->base, &lcdif_plane_helper_funcs); |
| ret = drm_universal_plane_init(dev, &lcdif_plane->base, possible_crtcs, |
| &lcdif_plane_funcs, lcdif_pixel_formats, |
| ARRAY_SIZE(lcdif_pixel_formats), NULL, |
| type, NULL); |
| if (ret) { |
| kfree(lcdif_plane); |
| return ERR_PTR(ret); |
| } |
| |
| ret = drm_plane_create_zpos_immutable_property(&lcdif_plane->base, zpos); |
| if (ret) { |
| kfree(lcdif_plane); |
| return ERR_PTR(ret); |
| } |
| |
| return lcdif_plane; |
| } |
| |
| void lcdif_plane_deinit(struct drm_device *dev, |
| struct lcdif_plane *lcdif_plane) |
| { |
| struct drm_plane *plane = &lcdif_plane->base; |
| |
| if (plane->zpos_property) |
| drm_property_destroy(dev, plane->zpos_property); |
| |
| lcdif_plane_destroy(plane); |
| } |