Files
2024-06-17 20:18:01 +08:00

477 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_fourcc.h>
#include <drm/drm_atomic.h>
#include <drm/drm_format_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include "lsdc_drv.h"
#include "lsdc_regs.h"
static const u32 lsdc_primary_formats[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_ARGB8888,
};
static const u32 lsdc_cursor_formats[] = {
DRM_FORMAT_ARGB8888,
};
static const u64 lsdc_fb_format_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID
};
static void lsdc_update_fb_format(struct lsdc_device *ldev,
struct drm_crtc *crtc,
const struct drm_format_info *fmt_info)
{
unsigned int index = drm_crtc_index(crtc);
u32 val = 0;
u32 fmt;
switch (fmt_info->format) {
case DRM_FORMAT_RGB565:
fmt = LSDC_PF_RGB565;
break;
case DRM_FORMAT_XRGB8888:
fmt = LSDC_PF_XRGB8888;
break;
case DRM_FORMAT_ARGB8888:
fmt = LSDC_PF_XRGB8888;
break;
default:
fmt = LSDC_PF_XRGB8888;
break;
}
if (index == 0) {
val = readl(ldev->reg_base + LSDC_CRTC0_CFG_REG);
val = (val & ~CFG_PIX_FMT_MASK) | fmt;
writel(val, ldev->reg_base + LSDC_CRTC0_CFG_REG);
} else if (index == 1) {
val = readl(ldev->reg_base + LSDC_CRTC1_CFG_REG);
val = (val & ~CFG_PIX_FMT_MASK) | fmt;
writel(val, ldev->reg_base + LSDC_CRTC1_CFG_REG);
}
}
static void lsdc_update_fb_start_addr(struct lsdc_device *ldev,
struct drm_crtc *crtc,
u64 paddr)
{
unsigned int index = drm_crtc_index(crtc);
u32 lo32_addr_reg;
u32 hi32_addr_reg;
u32 cfg_reg;
u32 val;
/*
* Find which framebuffer address register should update.
* if FB_ADDR0_REG is in using, we write the addr to FB_ADDR1_REG,
* if FB_ADDR1_REG is in using, we write the addr to FB_ADDR0_REG
*/
if (index == 0) {
/* CRTC0 */
val = readl(ldev->reg_base + LSDC_CRTC0_CFG_REG);
cfg_reg = LSDC_CRTC0_CFG_REG;
hi32_addr_reg = LSDC_CRTC0_FB_HI_ADDR_REG;
if (val & CFG_FB_IDX_BIT)
lo32_addr_reg = LSDC_CRTC0_FB_ADDR0_REG;
else
lo32_addr_reg = LSDC_CRTC0_FB_ADDR1_REG;
} else if (index == 1) {
/* CRTC1 */
val = readl(ldev->reg_base + LSDC_CRTC1_CFG_REG);
cfg_reg = LSDC_CRTC1_CFG_REG;
hi32_addr_reg = LSDC_CRTC1_FB_HI_ADDR_REG;
if (val & CFG_FB_IDX_BIT)
lo32_addr_reg = LSDC_CRTC1_FB_ADDR0_REG;
else
lo32_addr_reg = LSDC_CRTC1_FB_ADDR1_REG;
}
drm_dbg(ldev->ddev, "crtc%u scantout from 0x%llx\n", index, paddr);
/* The bridge's bus width is 40 */
writel(paddr, ldev->reg_base + lo32_addr_reg);
writel((paddr >> 32) & 0xFF, ldev->reg_base + hi32_addr_reg);
/*
* Then, we triger the fb switch, the switch of the framebuffer
* to be scanout will complete at the next vblank.
*/
writel(val | CFG_PAGE_FLIP_BIT, ldev->reg_base + cfg_reg);
}
static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb,
struct drm_plane_state *state,
unsigned int plane)
{
unsigned int offset = fb->offsets[plane];
offset += fb->format->cpp[plane] * (state->src_x >> 16);
offset += fb->pitches[plane] * (state->src_y >> 16);
return offset;
}
static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb)
{
struct drm_gem_vram_object *gbo;
s64 gpu_addr;
gbo = drm_gem_vram_of_gem(fb->obj[0]);
gpu_addr = drm_gem_vram_offset(gbo);
return gpu_addr;
}
static int lsdc_primary_plane_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_crtc *crtc = state->crtc;
struct drm_crtc_state *crtc_state;
struct drm_framebuffer *fb = state->fb;
/* no need for further checks if the plane is being disabled */
if (!crtc || !fb)
return 0;
crtc_state = drm_atomic_get_crtc_state(state->state, crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
return drm_atomic_helper_check_plane_state(plane->state,
crtc_state,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
false, true);
}
static void lsdc_update_stride(struct lsdc_device *ldev,
struct drm_crtc *crtc,
unsigned int stride)
{
unsigned int index = drm_crtc_index(crtc);
if (index == 0)
writel(stride, ldev->reg_base + LSDC_CRTC0_STRIDE_REG);
else if (index == 1)
writel(stride, ldev->reg_base + LSDC_CRTC1_STRIDE_REG);
drm_dbg(ldev->ddev, "update stride to %u\n", stride);
}
static void lsdc_primary_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct drm_device *ddev = plane->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
struct drm_plane_state *new_plane_state = plane->state;
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_framebuffer *fb = new_plane_state->fb;
u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0);
dma_addr_t fb_addr;
if (ldev->use_vram_helper) {
s64 gpu_addr;
gpu_addr = lsdc_get_vram_bo_offset(fb);
if (gpu_addr < 0)
return;
fb_addr = ldev->vram_base + gpu_addr + fb_offset;
} else {
struct drm_gem_cma_object *obj = drm_fb_cma_get_gem_obj(fb, 0);
fb_addr = obj->paddr + fb_offset;
}
lsdc_update_fb_start_addr(ldev, crtc, fb_addr);
lsdc_update_stride(ldev, crtc, fb->pitches[0]);
if (drm_atomic_crtc_needs_modeset(crtc->state))
lsdc_update_fb_format(ldev, crtc, fb->format);
}
static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
drm_dbg(plane->dev, "%s disabled\n", plane->name);
}
static int lsdc_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *new_state)
{
struct lsdc_device *ldev = to_lsdc(plane->dev);
if (ldev->use_vram_helper)
return drm_gem_vram_plane_helper_prepare_fb(plane, new_state);
return drm_gem_fb_prepare_fb(plane, new_state);
}
static void lsdc_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct drm_device *ddev = plane->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
if (ldev->use_vram_helper)
return drm_gem_vram_plane_helper_cleanup_fb(plane, old_state);
}
static const struct drm_plane_helper_funcs lsdc_primary_plane_helpers = {
.prepare_fb = lsdc_plane_prepare_fb,
.cleanup_fb = lsdc_plane_cleanup_fb,
.atomic_check = lsdc_primary_plane_atomic_check,
.atomic_update = lsdc_primary_plane_atomic_update,
.atomic_disable = lsdc_primary_plane_atomic_disable,
};
static int lsdc_cursor_atomic_check(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct drm_framebuffer *fb = state->fb;
struct drm_crtc *crtc = state->crtc;
struct drm_crtc_state *crtc_state;
/* no need for further checks if the plane is being disabled */
if (!crtc || !fb)
return 0;
if (!state->visible)
return 0;
crtc_state = drm_atomic_get_crtc_state(state->state, crtc);
if (WARN_ON(!crtc_state))
return -EINVAL;
return drm_atomic_helper_check_plane_state(state,
crtc_state,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
true,
true);
}
static void lsdc_cursor_atomic_update(struct drm_plane *plane,
struct drm_plane_state *old_plane_state)
{
struct lsdc_display_pipe * const dispipe = lsdc_cursor_to_dispipe(plane);
struct drm_device *ddev = plane->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
const struct lsdc_chip_desc * const descp = ldev->desc;
struct drm_plane_state *new_plane_state = plane->state;
struct drm_framebuffer *new_fb = new_plane_state->fb;
struct drm_framebuffer *old_fb = old_plane_state->fb;
int dst_x = new_plane_state->crtc_x;
int dst_y = new_plane_state->crtc_y;
u32 val;
if (new_fb != old_fb) {
u64 cursor_addr;
if (ldev->use_vram_helper) {
s64 offset;
offset = lsdc_get_vram_bo_offset(new_fb);
cursor_addr = ldev->vram_base + offset;
drm_dbg(ddev, "%s offset: %llx\n", plane->name, offset);
} else {
struct drm_gem_cma_object *cursor_obj;
cursor_obj = drm_fb_cma_get_gem_obj(new_fb, 0);
if (!cursor_obj)
return;
cursor_addr = cursor_obj->paddr;
}
if ((descp->chip == LSDC_CHIP_7A2000) && (dispipe->index == 1))
writel(cursor_addr, ldev->reg_base + LSDC_CURSOR1_ADDR_REG);
else
writel(cursor_addr, ldev->reg_base + LSDC_CURSOR0_ADDR_REG);
}
/* Update cursor's position */
if (dst_x < 0)
dst_x = 0;
if (dst_y < 0)
dst_y = 0;
val = (dst_y << 16) | dst_x;
if ((descp->chip == LSDC_CHIP_7A2000) && (dispipe->index == 1))
writel(val, ldev->reg_base + LSDC_CURSOR1_POSITION_REG);
else
writel(val, ldev->reg_base + LSDC_CURSOR0_POSITION_REG);
/* Update cursor's location and format */
val = CURSOR_FORMAT_ARGB8888;
if (descp->chip == LSDC_CHIP_7A2000) {
/* LS7A2000 support 64x64 and 32x32 */
val |= CURSOR_SIZE_64X64;
if (dispipe->index == 1) {
val |= CURSOR_LOCATION_BIT;
writel(val, ldev->reg_base + LSDC_CURSOR1_CFG_REG);
} else if (dispipe->index == 0) {
val &= ~CURSOR_LOCATION_BIT;
writel(val, ldev->reg_base + LSDC_CURSOR0_CFG_REG);
}
} else {
/*
* Update the location of the cursor
* if bit 4 of LSDC_CURSOR_CFG_REG is 1, then the cursor will be
* locate at CRTC1, if bit 4 of LSDC_CURSOR_CFG_REG is 0, then
* the cursor will be locate at CRTC0.
*/
if (dispipe->index)
val |= CURSOR_LOCATION_BIT;
writel(val, ldev->reg_base + LSDC_CURSOR0_CFG_REG);
}
}
static void lsdc_cursor_atomic_disable(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
const struct lsdc_display_pipe * const dispipe = lsdc_cursor_to_dispipe(plane);
struct drm_device *ddev = plane->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
const struct lsdc_chip_desc * const descp = ldev->desc;
u32 val;
if ((descp->chip == LSDC_CHIP_7A2000) && (dispipe->index == 1)) {
val = readl(ldev->reg_base + LSDC_CURSOR1_CFG_REG);
val &= ~CURSOR_FORMAT_MASK;
val |= CURSOR_FORMAT_DISABLE;
writel(val, ldev->reg_base + LSDC_CURSOR1_CFG_REG);
} else {
val = readl(ldev->reg_base + LSDC_CURSOR0_CFG_REG);
val &= ~CURSOR_FORMAT_MASK;
val |= CURSOR_FORMAT_DISABLE;
writel(val, ldev->reg_base + LSDC_CURSOR0_CFG_REG);
}
drm_dbg(ddev, "%s disabled\n", plane->name);
}
static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers = {
.prepare_fb = lsdc_plane_prepare_fb,
.cleanup_fb = lsdc_plane_cleanup_fb,
.atomic_check = lsdc_cursor_atomic_check,
.atomic_update = lsdc_cursor_atomic_update,
.atomic_disable = lsdc_cursor_atomic_disable,
};
static int lsdc_plane_get_default_zpos(enum drm_plane_type type)
{
switch (type) {
case DRM_PLANE_TYPE_PRIMARY:
return 0;
case DRM_PLANE_TYPE_OVERLAY:
return 1;
case DRM_PLANE_TYPE_CURSOR:
return 7;
}
return 0;
}
static void lsdc_plane_reset(struct drm_plane *plane)
{
drm_atomic_helper_plane_reset(plane);
plane->state->zpos = lsdc_plane_get_default_zpos(plane->type);
drm_dbg(plane->dev, "%s reset\n", plane->name);
}
static const struct drm_plane_funcs lsdc_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.destroy = drm_plane_cleanup,
.reset = lsdc_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
int lsdc_plane_init(struct lsdc_device *ldev,
struct drm_plane *plane,
enum drm_plane_type type,
unsigned int index)
{
struct drm_device *ddev = ldev->ddev;
int zpos = lsdc_plane_get_default_zpos(type);
unsigned int format_count;
const u32 *formats;
const char *name;
int ret;
switch (type) {
case DRM_PLANE_TYPE_PRIMARY:
formats = lsdc_primary_formats;
format_count = ARRAY_SIZE(lsdc_primary_formats);
name = "primary-%u";
break;
case DRM_PLANE_TYPE_CURSOR:
formats = lsdc_cursor_formats;
format_count = ARRAY_SIZE(lsdc_cursor_formats);
name = "cursor-%u";
break;
case DRM_PLANE_TYPE_OVERLAY:
drm_err(ddev, "overlay plane is not supported\n");
break;
}
ret = drm_universal_plane_init(ddev, plane, 1 << index,
&lsdc_plane_funcs,
formats, format_count,
lsdc_fb_format_modifiers,
type, name, index);
if (ret) {
drm_err(ddev, "%s failed: %d\n", __func__, ret);
return ret;
}
switch (type) {
case DRM_PLANE_TYPE_PRIMARY:
drm_plane_helper_add(plane, &lsdc_primary_plane_helpers);
drm_plane_create_zpos_property(plane, zpos, 0, 6);
break;
case DRM_PLANE_TYPE_CURSOR:
drm_plane_helper_add(plane, &lsdc_cursor_plane_helpers);
drm_plane_create_zpos_immutable_property(plane, zpos);
break;
case DRM_PLANE_TYPE_OVERLAY:
drm_err(ddev, "overlay plane is not supported\n");
break;
}
drm_plane_create_alpha_property(plane);
return 0;
}