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

455 lines
13 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2020 Loongson Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_device.h>
#include <drm/drm_crtc.h>
#include <drm/drm_plane.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_print.h>
#include "lsdc_drv.h"
#include "lsdc_regs.h"
static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
unsigned int val = lsdc_reg_read32(ldev, LSDC_INT_REG);
if (index == 0)
val |= INT_CRTC0_VS_EN;
else if (index == 1)
val |= INT_CRTC1_VS_EN;
else
DRM_ERROR("lsdc crtc is no more than 2\n");
lsdc_reg_write32(ldev, LSDC_INT_REG, val);
DRM_DEBUG_DRIVER("CRTC%u: vblank enabled\n", index);
return 0;
}
static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
unsigned int val = lsdc_reg_read32(ldev, LSDC_INT_REG);
if (index == 0)
val &= ~INT_CRTC0_VS_EN;
else if (index == 1)
val &= ~INT_CRTC1_VS_EN;
else
DRM_ERROR("lsdc crtc is no more than 2\n");
lsdc_reg_write32(ldev, LSDC_INT_REG, val);
DRM_DEBUG_DRIVER("CRTC%u: vblank disabled\n", index);
}
static void lsdc_crtc_destroy(struct drm_crtc *crtc)
{
DRM_DEBUG_DRIVER("CRTC%u destroyed\n", drm_crtc_index(crtc));
drm_crtc_cleanup(crtc);
}
/*
* CRTC got soft reset if bit 20 of CRTC*_CFG_REG from 1 to 0
*/
void lsdc_crtc_reset(struct drm_crtc *crtc)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
struct lsdc_display_pipe * const dispipe = drm_crtc_to_dispipe(crtc);
u32 val = CFG_RESET_BIT;
if (ldev->enable_gamma)
val |= CFG_GAMMAR_EN_BIT;
if (index == 0) {
DRM_DEBUG_DRIVER("Reset CRTC0\n");
if (dispipe->hw_clone) {
val |= CFG_PANEL_SWITCH;
DRM_DEBUG_DRIVER("display pipe 0 is clone to display pipe 1\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
} else if (index == 1) {
DRM_DEBUG_DRIVER("Reset CRTC1\n");
if (dispipe->hw_clone) {
val |= CFG_PANEL_SWITCH;
DRM_DEBUG_DRIVER("display pipe 1 is clone to display pipe 0\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
} else
DRM_ERROR("lsdc crtc is no more than 2\n");
drm_atomic_helper_crtc_reset(crtc);
}
/*
* These provide the minimum set of functions required to handle a CRTC
* Each driver is responsible for filling out this structure at startup time
*
* The drm_crtc_funcs structure is the central CRTC management structure
* in the DRM. Each CRTC controls one or more connectors
*/
static const struct drm_crtc_funcs lsdc_crtc_funcs = {
.reset = lsdc_crtc_reset,
.gamma_set = drm_atomic_helper_legacy_gamma_set,
.destroy = lsdc_crtc_destroy,
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = lsdc_crtc_enable_vblank,
.disable_vblank = lsdc_crtc_disable_vblank,
};
static enum drm_mode_status
lsdc_crtc_mode_valid(struct drm_crtc *crtc,
const struct drm_display_mode *mode)
{
if (mode->hdisplay % 16)
return MODE_BAD;
return MODE_OK;
}
/*
* @lsdc_crtc_mode_set_nofb:
*
* This callback is used to update the display mode of a CRTC without
* changing anything of the primary plane configuration. This fits the
* requirement of atomic and hence is used by the atomic helpers.
* It is also used by the transitional plane helpers to implement a
* @mode_set hook in drm_helper_crtc_mode_set().
*
* Note that the display pipe is completely off when this function is called.
* Atomic drivers which need hardware to be running before they program the
* new display mode (e.g. because they implement runtime PM) should not use
* this hook.
*
* This is because the helper library calls this hook only once per mode change
* and not every time the display pipeline is suspended using either DPMS or
* the new "ACTIVE" property. Which means register values set in this callback
* might get reset when the CRTC is suspended, but not restored. Such drivers
* should instead move all their CRTC setup into the @atomic_enable callback.
*
* This callback is optional.
*/
static void lsdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
unsigned int index = drm_crtc_index(crtc);
struct lsdc_display_pipe *dispipe = drm_crtc_to_dispipe(crtc);
struct lsdc_pll * const pixpll = &dispipe->pixpll;
unsigned int pixclock = mode->clock;
u32 val;
if (index == 0) {
/* CRTC 0 */
DRM_DEBUG_DRIVER("CRTC0 mode set no fb\n");
lsdc_reg_write32(ldev, LSDC_CRTC0_PANCFG_REG, 0x80001311);
lsdc_reg_write32(ldev, LSDC_CRTC0_PANTIM_REG, 0);
lsdc_reg_write32(ldev, LSDC_CRTC0_FB_ORIGIN_REG, 0);
/* hack 256 bytes align issue */
val = (mode->crtc_hdisplay + 63) & ~63;
val |= (mode->crtc_htotal << 16);
lsdc_reg_write32(ldev, LSDC_CRTC0_HDISPLAY_REG, val);
val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay;
lsdc_reg_write32(ldev, LSDC_CRTC0_VDISPLAY_REG, val);
/* HSync */
val = CFG_EN_HSYNC;
/* bits 26:16 hsync end */
val |= mode->crtc_hsync_end << 16;
/* bits 10:0 hsync start */
val |= mode->crtc_hsync_start;
if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
val |= CFG_INV_HSYNC;
DRM_DEBUG_DRIVER("CRTC0 HSync Inverted\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC0_HSYNC_REG, val);
/* VSync */
val = CFG_EN_VSYNC;
/* bits 26:16 vsync end */
val |= mode->crtc_vsync_end << 16;
/* bits 10:0 vsync start */
val |= mode->crtc_vsync_start;
if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
val |= CFG_INV_VSYNC;
DRM_DEBUG_DRIVER("CRTC0 VSync Inverted\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC0_VSYNC_REG, val);
} else if (index == 1) {
/* CRTC 1 */
DRM_DEBUG_DRIVER("CRTC1 mode set no fb\n");
lsdc_reg_write32(ldev, LSDC_CRTC1_PANCFG_REG, 0x80001311);
lsdc_reg_write32(ldev, LSDC_CRTC1_PANTIM_REG, 0);
lsdc_reg_write32(ldev, LSDC_CRTC1_FB_ORIGIN_REG, 0);
/* hack 256 byte align issue */
val = (mode->crtc_hdisplay + 63) & ~63;
val |= (mode->crtc_htotal << 16);
lsdc_reg_write32(ldev, LSDC_CRTC1_HDISPLAY_REG, val);
lsdc_reg_write32(ldev, LSDC_CRTC1_VDISPLAY_REG,
(mode->crtc_vtotal << 16) | mode->crtc_vdisplay);
/* HSYNC */
val = CFG_EN_HSYNC;
/* bits 26:16 hsync end */
val |= mode->crtc_hsync_end << 16;
/* bits 10:0 hsync start */
val |= mode->crtc_hsync_start;
if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
val |= CFG_INV_HSYNC;
DRM_DEBUG_DRIVER("CRTC1 HSync Inverted\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC1_HSYNC_REG, val);
/* VSYNC */
val = CFG_EN_VSYNC;
/* bits 26:16 vsync end */
val |= mode->crtc_vsync_end << 16;
/* bits 10:0 vsync start */
val |= mode->crtc_vsync_start;
if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
val |= CFG_INV_VSYNC;
DRM_DEBUG_DRIVER("CRTC1 VSync Inverted\n");
}
lsdc_reg_write32(ldev, LSDC_CRTC1_VSYNC_REG, val);
} else
DRM_DEBUG_DRIVER("CRTC is no more than 2\n");
/* config the pixel pll */
if (pixpll->funcs->find_pll_param(pixpll, pixclock) == false)
pixpll->funcs->compute_clock(pixpll, pixclock);
pixpll->funcs->config_pll(pixpll);
}
static void lsdc_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
u32 val;
if (index == 0) {
val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
val |= CFG_OUTPUT_ENABLE;
lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
} else if (index == 1) {
val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
val |= CFG_OUTPUT_ENABLE;
lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
} else
DRM_ERROR("CRTC is no more than 2\n");
drm_crtc_vblank_on(crtc);
DRM_DEBUG_DRIVER("CRTC%u enabled\n", index);
}
static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
u32 val;
drm_crtc_vblank_off(crtc);
if (index == 0) {
val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
val &= ~CFG_OUTPUT_ENABLE;
lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
} else if (index == 1) {
val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
val &= ~CFG_OUTPUT_ENABLE;
lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
} else
DRM_ERROR("CRTC is no more than 2\n");
DRM_DEBUG_DRIVER("CRTC%u disabled\n", index);
}
static void lsdc_crtc_update_clut(struct drm_crtc *crtc)
{
struct lsdc_device *ldev = to_lsdc(crtc->dev);
unsigned int index = drm_crtc_index(crtc);
struct drm_color_lut *lut;
unsigned int i;
if (!ldev->enable_gamma)
return;
if (!crtc->state->color_mgmt_changed || !crtc->state->gamma_lut)
return;
lut = (struct drm_color_lut *)crtc->state->gamma_lut->data;
if (index == 0)
lsdc_reg_write32(ldev, LSDC_CRTC0_GAMMA_INDEX_REG, 0);
else if (index == 1)
lsdc_reg_write32(ldev, LSDC_CRTC1_GAMMA_INDEX_REG, 0);
for (i = 0; i < LSDC_CLUT_SIZE; i++) {
u32 val = ((lut->red << 8) & 0xff0000) |
(lut->green & 0xff00) |
(lut->blue >> 8) ;
if (index == 0)
lsdc_reg_write32(ldev, LSDC_CRTC0_GAMMA_DATA_REG, val);
else if (index == 1)
lsdc_reg_write32(ldev, LSDC_CRTC1_GAMMA_DATA_REG, val);
lut++;
}
}
/**
* @lsdc_crtc_atomic_flush:
*
* should finalize an atomic update of multiple planes on a CRTC in this hook.
* Depending upon hardware this might include checking that vblank evasion
* was successful, unblocking updates by setting bits or setting the GO bit
* to flush out all updates.
*
* Simple hardware or hardware with special requirements can commit and
* flush out all updates for all planes from this hook and forgo all the
* other commit hooks for plane updates.
*
* This hook is called after any plane commit functions are called.
*
* Note that the power state of the display pipe when this function is
* called depends upon the exact helpers and calling sequence the driver
* has picked. See drm_atomic_helper_commit_planes() for a discussion of
* the tradeoffs and variants of plane commit helpers.
*
* This callback is used by the atomic modeset helpers and by the
* transitional plane helpers, but it is optional.
*/
static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct drm_pending_vblank_event *event = crtc->state->event;
lsdc_crtc_update_clut(crtc);
if (event) {
crtc->state->event = NULL;
spin_lock_irq(&crtc->dev->event_lock);
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
}
/**
* These provide the minimum set of functions required to handle a CRTC
*
* The drm_crtc_helper_funcs is a helper operations for CRTC
*/
static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = {
.mode_valid = lsdc_crtc_mode_valid,
.mode_set_nofb = lsdc_crtc_mode_set_nofb,
.atomic_enable = lsdc_crtc_atomic_enable,
.atomic_disable = lsdc_crtc_atomic_disable,
.atomic_flush = lsdc_crtc_atomic_flush,
};
/*
* lsdc_crtc_init
*
* @ldev: point to the lsdc_device structure
*
* Init CRTC
*/
int lsdc_crtc_init(struct drm_device *ddev,
unsigned int index,
struct drm_crtc *crtc,
struct drm_plane *primary_plane,
struct drm_plane *cursor_plane)
{
int ret;
ret = drm_crtc_init_with_planes(ddev,
crtc,
primary_plane,
cursor_plane,
&lsdc_crtc_funcs,
NULL);
if (ret)
DRM_ERROR("crtc init with planes failed\n");
ret = drm_mode_crtc_set_gamma_size(crtc, LSDC_CLUT_SIZE);
if (ret)
DRM_WARN("set the gamma table size failed\n");
drm_crtc_enable_color_mgmt(crtc, 0, false, LSDC_CLUT_SIZE);
drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs);
return ret;
}