blob: a29fd3ddc6f528524ee3a755c01cccd902172c3c [file] [log] [blame]
/*
* 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);
}