9aa99bb197
This failed to build here after the header rework.
Fixes: 33d5ae6cac
("drm/print: drop include debugfs.h and include where needed")
Signed-off-by: Dave Airlie <airlied@redhat.com>
1026 lines
27 KiB
C
1026 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2023 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_debugfs.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include "lsdc_drv.h"
|
|
|
|
/*
|
|
* After the CRTC soft reset, the vblank counter would be reset to zero.
|
|
* But the address and other settings in the CRTC register remain the same
|
|
* as before.
|
|
*/
|
|
|
|
static void lsdc_crtc0_soft_reset(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG);
|
|
|
|
val &= CFG_VALID_BITS_MASK;
|
|
|
|
/* Soft reset bit, active low */
|
|
val &= ~CFG_RESET_N;
|
|
|
|
val &= ~CFG_PIX_FMT_MASK;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val);
|
|
|
|
udelay(1);
|
|
|
|
val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val);
|
|
|
|
/* Wait about a vblank time */
|
|
mdelay(20);
|
|
}
|
|
|
|
static void lsdc_crtc1_soft_reset(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG);
|
|
|
|
val &= CFG_VALID_BITS_MASK;
|
|
|
|
/* Soft reset bit, active low */
|
|
val &= ~CFG_RESET_N;
|
|
|
|
val &= ~CFG_PIX_FMT_MASK;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val);
|
|
|
|
udelay(1);
|
|
|
|
val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val);
|
|
|
|
/* Wait about a vblank time */
|
|
msleep(20);
|
|
}
|
|
|
|
static void lsdc_crtc0_enable(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG);
|
|
|
|
/*
|
|
* This may happen in extremely rare cases, but a soft reset can
|
|
* bring it back to normal. We add a warning here, hoping to catch
|
|
* something if it happens.
|
|
*/
|
|
if (val & CRTC_ANCHORED) {
|
|
drm_warn(&ldev->base, "%s stall\n", lcrtc->base.name);
|
|
return lsdc_crtc0_soft_reset(lcrtc);
|
|
}
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val | CFG_OUTPUT_ENABLE);
|
|
}
|
|
|
|
static void lsdc_crtc0_disable(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_clr(ldev, LSDC_CRTC0_CFG_REG, CFG_OUTPUT_ENABLE);
|
|
|
|
udelay(9);
|
|
}
|
|
|
|
static void lsdc_crtc1_enable(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
/*
|
|
* This may happen in extremely rare cases, but a soft reset can
|
|
* bring it back to normal. We add a warning here, hoping to catch
|
|
* something if it happens.
|
|
*/
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG);
|
|
if (val & CRTC_ANCHORED) {
|
|
drm_warn(&ldev->base, "%s stall\n", lcrtc->base.name);
|
|
return lsdc_crtc1_soft_reset(lcrtc);
|
|
}
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val | CFG_OUTPUT_ENABLE);
|
|
}
|
|
|
|
static void lsdc_crtc1_disable(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_clr(ldev, LSDC_CRTC1_CFG_REG, CFG_OUTPUT_ENABLE);
|
|
|
|
udelay(9);
|
|
}
|
|
|
|
/* All Loongson display controllers have hardware scanout position recoders */
|
|
|
|
static void lsdc_crtc0_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG);
|
|
|
|
*hpos = val >> 16;
|
|
*vpos = val & 0xffff;
|
|
}
|
|
|
|
static void lsdc_crtc1_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val;
|
|
|
|
val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG);
|
|
|
|
*hpos = val >> 16;
|
|
*vpos = val & 0xffff;
|
|
}
|
|
|
|
static void lsdc_crtc0_enable_vblank(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN);
|
|
}
|
|
|
|
static void lsdc_crtc0_disable_vblank(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN);
|
|
}
|
|
|
|
static void lsdc_crtc1_enable_vblank(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN);
|
|
}
|
|
|
|
static void lsdc_crtc1_disable_vblank(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN);
|
|
}
|
|
|
|
static void lsdc_crtc0_flip(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_PAGE_FLIP);
|
|
}
|
|
|
|
static void lsdc_crtc1_flip(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_PAGE_FLIP);
|
|
}
|
|
|
|
/*
|
|
* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic
|
|
* This may be useful for custom cloning (TWIN) applications. Saving the
|
|
* bandwidth compared with the clone (mirroring) display mode provided by
|
|
* drm core.
|
|
*/
|
|
|
|
static void lsdc_crtc0_clone(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_HW_CLONE);
|
|
}
|
|
|
|
static void lsdc_crtc1_clone(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_HW_CLONE);
|
|
}
|
|
|
|
static void lsdc_crtc0_set_mode(struct lsdc_crtc *lcrtc,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG,
|
|
(mode->crtc_htotal << 16) | mode->crtc_hdisplay);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG,
|
|
(mode->crtc_vtotal << 16) | mode->crtc_vdisplay);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG,
|
|
(mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG,
|
|
(mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN);
|
|
}
|
|
|
|
static void lsdc_crtc1_set_mode(struct lsdc_crtc *lcrtc,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG,
|
|
(mode->crtc_htotal << 16) | mode->crtc_hdisplay);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG,
|
|
(mode->crtc_vtotal << 16) | mode->crtc_vdisplay);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG,
|
|
(mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN);
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG,
|
|
(mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN);
|
|
}
|
|
|
|
/*
|
|
* This is required for S3 support.
|
|
* After resuming from suspend, LSDC_CRTCx_CFG_REG (x = 0 or 1) is filled
|
|
* with garbage value, which causes the CRTC hang there.
|
|
*
|
|
* This function provides minimal settings for the affected registers.
|
|
* This overrides the firmware's settings on startup, making the CRTC work
|
|
* on our own, similar to the functional of GPU POST (Power On Self Test).
|
|
* Only touch CRTC hardware-related parts.
|
|
*/
|
|
|
|
static void lsdc_crtc0_reset(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, CFG_RESET_N | LSDC_PF_XRGB8888);
|
|
}
|
|
|
|
static void lsdc_crtc1_reset(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, CFG_RESET_N | LSDC_PF_XRGB8888);
|
|
}
|
|
|
|
static const struct lsdc_crtc_hw_ops ls7a1000_crtc_hw_ops[2] = {
|
|
{
|
|
.enable = lsdc_crtc0_enable,
|
|
.disable = lsdc_crtc0_disable,
|
|
.enable_vblank = lsdc_crtc0_enable_vblank,
|
|
.disable_vblank = lsdc_crtc0_disable_vblank,
|
|
.flip = lsdc_crtc0_flip,
|
|
.clone = lsdc_crtc0_clone,
|
|
.set_mode = lsdc_crtc0_set_mode,
|
|
.get_scan_pos = lsdc_crtc0_scan_pos,
|
|
.soft_reset = lsdc_crtc0_soft_reset,
|
|
.reset = lsdc_crtc0_reset,
|
|
},
|
|
{
|
|
.enable = lsdc_crtc1_enable,
|
|
.disable = lsdc_crtc1_disable,
|
|
.enable_vblank = lsdc_crtc1_enable_vblank,
|
|
.disable_vblank = lsdc_crtc1_disable_vblank,
|
|
.flip = lsdc_crtc1_flip,
|
|
.clone = lsdc_crtc1_clone,
|
|
.set_mode = lsdc_crtc1_set_mode,
|
|
.get_scan_pos = lsdc_crtc1_scan_pos,
|
|
.soft_reset = lsdc_crtc1_soft_reset,
|
|
.reset = lsdc_crtc1_reset,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* The 32-bit hardware vblank counter has been available since LS7A2000
|
|
* and LS2K2000. The counter increases even though the CRTC is disabled,
|
|
* it will be reset only if the CRTC is being soft reset.
|
|
* Those registers are also readable for ls7a1000, but its value does not
|
|
* change.
|
|
*/
|
|
|
|
static u32 lsdc_crtc0_get_vblank_count(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG);
|
|
}
|
|
|
|
static u32 lsdc_crtc1_get_vblank_count(struct lsdc_crtc *lcrtc)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
|
|
return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG);
|
|
}
|
|
|
|
/*
|
|
* The DMA step bit fields are available since LS7A2000/LS2K2000, for
|
|
* supporting odd resolutions. But a large DMA step save the bandwidth.
|
|
* The larger, the better. Behavior of writing those bits on LS7A1000
|
|
* or LS2K1000 is underfined.
|
|
*/
|
|
|
|
static void lsdc_crtc0_set_dma_step(struct lsdc_crtc *lcrtc,
|
|
enum lsdc_dma_steps dma_step)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG);
|
|
|
|
val &= ~CFG_DMA_STEP_MASK;
|
|
val |= dma_step << CFG_DMA_STEP_SHIFT;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val);
|
|
}
|
|
|
|
static void lsdc_crtc1_set_dma_step(struct lsdc_crtc *lcrtc,
|
|
enum lsdc_dma_steps dma_step)
|
|
{
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
u32 val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG);
|
|
|
|
val &= ~CFG_DMA_STEP_MASK;
|
|
val |= dma_step << CFG_DMA_STEP_SHIFT;
|
|
|
|
lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val);
|
|
}
|
|
|
|
static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = {
|
|
{
|
|
.enable = lsdc_crtc0_enable,
|
|
.disable = lsdc_crtc0_disable,
|
|
.enable_vblank = lsdc_crtc0_enable_vblank,
|
|
.disable_vblank = lsdc_crtc0_disable_vblank,
|
|
.flip = lsdc_crtc0_flip,
|
|
.clone = lsdc_crtc0_clone,
|
|
.set_mode = lsdc_crtc0_set_mode,
|
|
.soft_reset = lsdc_crtc0_soft_reset,
|
|
.get_scan_pos = lsdc_crtc0_scan_pos,
|
|
.set_dma_step = lsdc_crtc0_set_dma_step,
|
|
.get_vblank_counter = lsdc_crtc0_get_vblank_count,
|
|
.reset = lsdc_crtc0_reset,
|
|
},
|
|
{
|
|
.enable = lsdc_crtc1_enable,
|
|
.disable = lsdc_crtc1_disable,
|
|
.enable_vblank = lsdc_crtc1_enable_vblank,
|
|
.disable_vblank = lsdc_crtc1_disable_vblank,
|
|
.flip = lsdc_crtc1_flip,
|
|
.clone = lsdc_crtc1_clone,
|
|
.set_mode = lsdc_crtc1_set_mode,
|
|
.get_scan_pos = lsdc_crtc1_scan_pos,
|
|
.soft_reset = lsdc_crtc1_soft_reset,
|
|
.set_dma_step = lsdc_crtc1_set_dma_step,
|
|
.get_vblank_counter = lsdc_crtc1_get_vblank_count,
|
|
.reset = lsdc_crtc1_reset,
|
|
},
|
|
};
|
|
|
|
static void lsdc_crtc_reset(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops;
|
|
struct lsdc_crtc_state *priv_crtc_state;
|
|
|
|
if (crtc->state)
|
|
crtc->funcs->atomic_destroy_state(crtc, crtc->state);
|
|
|
|
priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL);
|
|
|
|
if (!priv_crtc_state)
|
|
__drm_atomic_helper_crtc_reset(crtc, NULL);
|
|
else
|
|
__drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base);
|
|
|
|
/* Reset the CRTC hardware, this is required for S3 support */
|
|
ops->reset(lcrtc);
|
|
}
|
|
|
|
static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *state)
|
|
{
|
|
struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state);
|
|
|
|
__drm_atomic_helper_crtc_destroy_state(&priv_state->base);
|
|
|
|
kfree(priv_state);
|
|
}
|
|
|
|
static struct drm_crtc_state *
|
|
lsdc_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc_state *new_priv_state;
|
|
struct lsdc_crtc_state *old_priv_state;
|
|
|
|
new_priv_state = kzalloc(sizeof(*new_priv_state), GFP_KERNEL);
|
|
if (!new_priv_state)
|
|
return NULL;
|
|
|
|
__drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->base);
|
|
|
|
old_priv_state = to_lsdc_crtc_state(crtc->state);
|
|
|
|
memcpy(&new_priv_state->pparms, &old_priv_state->pparms,
|
|
sizeof(new_priv_state->pparms));
|
|
|
|
return &new_priv_state->base;
|
|
}
|
|
|
|
static u32 lsdc_crtc_get_vblank_counter(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
|
|
/* 32-bit hardware vblank counter */
|
|
return lcrtc->hw_ops->get_vblank_counter(lcrtc);
|
|
}
|
|
|
|
static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
|
|
if (!lcrtc->has_vblank)
|
|
return -EINVAL;
|
|
|
|
lcrtc->hw_ops->enable_vblank(lcrtc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
|
|
if (!lcrtc->has_vblank)
|
|
return;
|
|
|
|
lcrtc->hw_ops->disable_vblank(lcrtc);
|
|
}
|
|
|
|
/*
|
|
* CRTC related debugfs
|
|
* Primary planes and cursor planes belong to the CRTC as well.
|
|
* For the sake of convenience, plane-related registers are also add here.
|
|
*/
|
|
|
|
#define REG_DEF(reg) { \
|
|
.name = __stringify_1(LSDC_##reg##_REG), \
|
|
.offset = LSDC_##reg##_REG, \
|
|
}
|
|
|
|
static const struct lsdc_reg32 lsdc_crtc_regs_array[2][21] = {
|
|
[0] = {
|
|
REG_DEF(CRTC0_CFG),
|
|
REG_DEF(CRTC0_FB_ORIGIN),
|
|
REG_DEF(CRTC0_DVO_CONF),
|
|
REG_DEF(CRTC0_HDISPLAY),
|
|
REG_DEF(CRTC0_HSYNC),
|
|
REG_DEF(CRTC0_VDISPLAY),
|
|
REG_DEF(CRTC0_VSYNC),
|
|
REG_DEF(CRTC0_GAMMA_INDEX),
|
|
REG_DEF(CRTC0_GAMMA_DATA),
|
|
REG_DEF(CRTC0_SYNC_DEVIATION),
|
|
REG_DEF(CRTC0_VSYNC_COUNTER),
|
|
REG_DEF(CRTC0_SCAN_POS),
|
|
REG_DEF(CRTC0_STRIDE),
|
|
REG_DEF(CRTC0_FB1_ADDR_HI),
|
|
REG_DEF(CRTC0_FB1_ADDR_LO),
|
|
REG_DEF(CRTC0_FB0_ADDR_HI),
|
|
REG_DEF(CRTC0_FB0_ADDR_LO),
|
|
REG_DEF(CURSOR0_CFG),
|
|
REG_DEF(CURSOR0_POSITION),
|
|
REG_DEF(CURSOR0_BG_COLOR),
|
|
REG_DEF(CURSOR0_FG_COLOR),
|
|
},
|
|
[1] = {
|
|
REG_DEF(CRTC1_CFG),
|
|
REG_DEF(CRTC1_FB_ORIGIN),
|
|
REG_DEF(CRTC1_DVO_CONF),
|
|
REG_DEF(CRTC1_HDISPLAY),
|
|
REG_DEF(CRTC1_HSYNC),
|
|
REG_DEF(CRTC1_VDISPLAY),
|
|
REG_DEF(CRTC1_VSYNC),
|
|
REG_DEF(CRTC1_GAMMA_INDEX),
|
|
REG_DEF(CRTC1_GAMMA_DATA),
|
|
REG_DEF(CRTC1_SYNC_DEVIATION),
|
|
REG_DEF(CRTC1_VSYNC_COUNTER),
|
|
REG_DEF(CRTC1_SCAN_POS),
|
|
REG_DEF(CRTC1_STRIDE),
|
|
REG_DEF(CRTC1_FB1_ADDR_HI),
|
|
REG_DEF(CRTC1_FB1_ADDR_LO),
|
|
REG_DEF(CRTC1_FB0_ADDR_HI),
|
|
REG_DEF(CRTC1_FB0_ADDR_LO),
|
|
REG_DEF(CURSOR1_CFG),
|
|
REG_DEF(CURSOR1_POSITION),
|
|
REG_DEF(CURSOR1_BG_COLOR),
|
|
REG_DEF(CURSOR1_FG_COLOR),
|
|
},
|
|
};
|
|
|
|
static int lsdc_crtc_show_regs(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data;
|
|
struct lsdc_device *ldev = lcrtc->ldev;
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < lcrtc->nreg; i++) {
|
|
const struct lsdc_reg32 *preg = &lcrtc->preg[i];
|
|
u32 offset = preg->offset;
|
|
|
|
seq_printf(m, "%s (0x%04x): 0x%08x\n",
|
|
preg->name, offset, lsdc_rreg32(ldev, offset));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_crtc_show_scan_position(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data;
|
|
int x, y;
|
|
|
|
lcrtc->hw_ops->get_scan_pos(lcrtc, &x, &y);
|
|
seq_printf(m, "Scanout position: x: %08u, y: %08u\n", x, y);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_crtc_show_vblank_counter(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data;
|
|
|
|
if (lcrtc->hw_ops->get_vblank_counter)
|
|
seq_printf(m, "%s vblank counter: %08u\n\n", lcrtc->base.name,
|
|
lcrtc->hw_ops->get_vblank_counter(lcrtc));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_pixpll_show_clock(struct seq_file *m, void *arg)
|
|
{
|
|
struct drm_info_node *node = (struct drm_info_node *)m->private;
|
|
struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data;
|
|
struct lsdc_pixpll *pixpll = &lcrtc->pixpll;
|
|
const struct lsdc_pixpll_funcs *funcs = pixpll->funcs;
|
|
struct drm_crtc *crtc = &lcrtc->base;
|
|
struct drm_display_mode *mode = &crtc->state->mode;
|
|
struct drm_printer printer = drm_seq_file_printer(m);
|
|
unsigned int out_khz;
|
|
|
|
out_khz = funcs->get_rate(pixpll);
|
|
|
|
seq_printf(m, "%s: %dx%d@%d\n", crtc->name,
|
|
mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode));
|
|
|
|
seq_printf(m, "Pixel clock required: %d kHz\n", mode->clock);
|
|
seq_printf(m, "Actual frequency output: %u kHz\n", out_khz);
|
|
seq_printf(m, "Diff: %d kHz\n", out_khz - mode->clock);
|
|
|
|
funcs->print(pixpll, &printer);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct drm_info_list lsdc_crtc_debugfs_list[2][4] = {
|
|
[0] = {
|
|
{ "regs", lsdc_crtc_show_regs, 0, NULL },
|
|
{ "pixclk", lsdc_pixpll_show_clock, 0, NULL },
|
|
{ "scanpos", lsdc_crtc_show_scan_position, 0, NULL },
|
|
{ "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL },
|
|
},
|
|
[1] = {
|
|
{ "regs", lsdc_crtc_show_regs, 0, NULL },
|
|
{ "pixclk", lsdc_pixpll_show_clock, 0, NULL },
|
|
{ "scanpos", lsdc_crtc_show_scan_position, 0, NULL },
|
|
{ "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL },
|
|
},
|
|
};
|
|
|
|
/* operate manually */
|
|
|
|
static int lsdc_crtc_man_op_show(struct seq_file *m, void *data)
|
|
{
|
|
seq_puts(m, "soft_reset: soft reset this CRTC\n");
|
|
seq_puts(m, "enable: enable this CRTC\n");
|
|
seq_puts(m, "disable: disable this CRTC\n");
|
|
seq_puts(m, "flip: trigger the page flip\n");
|
|
seq_puts(m, "clone: clone the another crtc with hardware logic\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_crtc_man_op_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct drm_crtc *crtc = inode->i_private;
|
|
|
|
return single_open(file, lsdc_crtc_man_op_show, crtc);
|
|
}
|
|
|
|
static ssize_t lsdc_crtc_man_op_write(struct file *file,
|
|
const char __user *ubuf,
|
|
size_t len,
|
|
loff_t *offp)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct lsdc_crtc *lcrtc = m->private;
|
|
const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops;
|
|
char buf[16];
|
|
|
|
if (len > sizeof(buf) - 1)
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(buf, ubuf, len))
|
|
return -EFAULT;
|
|
|
|
buf[len] = '\0';
|
|
|
|
if (sysfs_streq(buf, "soft_reset"))
|
|
ops->soft_reset(lcrtc);
|
|
else if (sysfs_streq(buf, "enable"))
|
|
ops->enable(lcrtc);
|
|
else if (sysfs_streq(buf, "disable"))
|
|
ops->disable(lcrtc);
|
|
else if (sysfs_streq(buf, "flip"))
|
|
ops->flip(lcrtc);
|
|
else if (sysfs_streq(buf, "clone"))
|
|
ops->clone(lcrtc);
|
|
|
|
return len;
|
|
}
|
|
|
|
static const struct file_operations lsdc_crtc_man_op_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = lsdc_crtc_man_op_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
.write = lsdc_crtc_man_op_write,
|
|
};
|
|
|
|
static int lsdc_crtc_late_register(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc);
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
struct drm_minor *minor = crtc->dev->primary;
|
|
unsigned int index = dispipe->index;
|
|
unsigned int i;
|
|
|
|
lcrtc->preg = lsdc_crtc_regs_array[index];
|
|
lcrtc->nreg = ARRAY_SIZE(lsdc_crtc_regs_array[index]);
|
|
lcrtc->p_info_list = lsdc_crtc_debugfs_list[index];
|
|
lcrtc->n_info_list = ARRAY_SIZE(lsdc_crtc_debugfs_list[index]);
|
|
|
|
for (i = 0; i < lcrtc->n_info_list; ++i)
|
|
lcrtc->p_info_list[i].data = lcrtc;
|
|
|
|
drm_debugfs_create_files(lcrtc->p_info_list, lcrtc->n_info_list,
|
|
crtc->debugfs_entry, minor);
|
|
|
|
/* Manual operations supported */
|
|
debugfs_create_file("ops", 0644, crtc->debugfs_entry, lcrtc,
|
|
&lsdc_crtc_man_op_fops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lsdc_crtc_atomic_print_state(struct drm_printer *p,
|
|
const struct drm_crtc_state *state)
|
|
{
|
|
const struct lsdc_crtc_state *priv_state;
|
|
const struct lsdc_pixpll_parms *pparms;
|
|
|
|
priv_state = container_of_const(state, struct lsdc_crtc_state, base);
|
|
pparms = &priv_state->pparms;
|
|
|
|
drm_printf(p, "\tInput clock divider = %u\n", pparms->div_ref);
|
|
drm_printf(p, "\tMedium clock multiplier = %u\n", pparms->loopc);
|
|
drm_printf(p, "\tOutput clock divider = %u\n", pparms->div_out);
|
|
}
|
|
|
|
static const struct drm_crtc_funcs ls7a1000_crtc_funcs = {
|
|
.reset = lsdc_crtc_reset,
|
|
.destroy = drm_crtc_cleanup,
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state,
|
|
.atomic_destroy_state = lsdc_crtc_atomic_destroy_state,
|
|
.late_register = lsdc_crtc_late_register,
|
|
.enable_vblank = lsdc_crtc_enable_vblank,
|
|
.disable_vblank = lsdc_crtc_disable_vblank,
|
|
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
|
|
.atomic_print_state = lsdc_crtc_atomic_print_state,
|
|
};
|
|
|
|
static const struct drm_crtc_funcs ls7a2000_crtc_funcs = {
|
|
.reset = lsdc_crtc_reset,
|
|
.destroy = drm_crtc_cleanup,
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state,
|
|
.atomic_destroy_state = lsdc_crtc_atomic_destroy_state,
|
|
.late_register = lsdc_crtc_late_register,
|
|
.get_vblank_counter = lsdc_crtc_get_vblank_counter,
|
|
.enable_vblank = lsdc_crtc_enable_vblank,
|
|
.disable_vblank = lsdc_crtc_disable_vblank,
|
|
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
|
|
.atomic_print_state = lsdc_crtc_atomic_print_state,
|
|
};
|
|
|
|
static enum drm_mode_status
|
|
lsdc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
|
|
{
|
|
struct drm_device *ddev = crtc->dev;
|
|
struct lsdc_device *ldev = to_lsdc(ddev);
|
|
const struct lsdc_desc *descp = ldev->descp;
|
|
unsigned int pitch;
|
|
|
|
if (mode->hdisplay > descp->max_width)
|
|
return MODE_BAD_HVALUE;
|
|
|
|
if (mode->vdisplay > descp->max_height)
|
|
return MODE_BAD_VVALUE;
|
|
|
|
if (mode->clock > descp->max_pixel_clk) {
|
|
drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n",
|
|
mode->hdisplay, mode->vdisplay, mode->clock);
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
|
|
/* 4 for DRM_FORMAT_XRGB8888 */
|
|
pitch = mode->hdisplay * 4;
|
|
|
|
if (pitch % descp->pitch_align) {
|
|
drm_dbg_kms(ddev, "align to %u bytes is required: %u\n",
|
|
descp->pitch_align, pitch);
|
|
return MODE_BAD_WIDTH;
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *state)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
struct lsdc_pixpll *pixpll = &lcrtc->pixpll;
|
|
const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs;
|
|
struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state);
|
|
unsigned int clock = state->mode.clock;
|
|
int ret;
|
|
|
|
ret = pfuncs->compute(pixpll, clock, &priv_state->pparms);
|
|
if (ret) {
|
|
drm_warn(crtc->dev, "Failed to find PLL params for %ukHz\n",
|
|
clock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
|
|
|
if (!crtc_state->enable)
|
|
return 0;
|
|
|
|
return lsdc_pixpll_atomic_check(crtc, crtc_state);
|
|
}
|
|
|
|
static void lsdc_crtc_mode_set_nofb(struct drm_crtc *crtc)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
const struct lsdc_crtc_hw_ops *crtc_hw_ops = lcrtc->hw_ops;
|
|
struct lsdc_pixpll *pixpll = &lcrtc->pixpll;
|
|
const struct lsdc_pixpll_funcs *pixpll_funcs = pixpll->funcs;
|
|
struct drm_crtc_state *state = crtc->state;
|
|
struct drm_display_mode *mode = &state->mode;
|
|
struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state);
|
|
|
|
pixpll_funcs->update(pixpll, &priv_state->pparms);
|
|
|
|
if (crtc_hw_ops->set_dma_step) {
|
|
unsigned int width_in_bytes = mode->hdisplay * 4;
|
|
enum lsdc_dma_steps dma_step;
|
|
|
|
/*
|
|
* Using DMA step as large as possible, for improving
|
|
* hardware DMA efficiency.
|
|
*/
|
|
if (width_in_bytes % 256 == 0)
|
|
dma_step = LSDC_DMA_STEP_256_BYTES;
|
|
else if (width_in_bytes % 128 == 0)
|
|
dma_step = LSDC_DMA_STEP_128_BYTES;
|
|
else if (width_in_bytes % 64 == 0)
|
|
dma_step = LSDC_DMA_STEP_64_BYTES;
|
|
else /* width_in_bytes % 32 == 0 */
|
|
dma_step = LSDC_DMA_STEP_32_BYTES;
|
|
|
|
crtc_hw_ops->set_dma_step(lcrtc, dma_step);
|
|
}
|
|
|
|
crtc_hw_ops->set_mode(lcrtc, mode);
|
|
}
|
|
|
|
static void lsdc_crtc_send_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *ddev = crtc->dev;
|
|
unsigned long flags;
|
|
|
|
if (!crtc->state || !crtc->state->event)
|
|
return;
|
|
|
|
drm_dbg(ddev, "Send vblank manually\n");
|
|
|
|
spin_lock_irqsave(&ddev->event_lock, flags);
|
|
drm_crtc_send_vblank_event(crtc, crtc->state->event);
|
|
crtc->state->event = NULL;
|
|
spin_unlock_irqrestore(&ddev->event_lock, flags);
|
|
}
|
|
|
|
static void lsdc_crtc_atomic_enable(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
|
|
if (lcrtc->has_vblank)
|
|
drm_crtc_vblank_on(crtc);
|
|
|
|
lcrtc->hw_ops->enable(lcrtc);
|
|
}
|
|
|
|
static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
|
|
if (lcrtc->has_vblank)
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
lcrtc->hw_ops->disable(lcrtc);
|
|
|
|
/*
|
|
* Make sure we issue a vblank event after disabling the CRTC if
|
|
* someone was waiting it.
|
|
*/
|
|
lsdc_crtc_send_vblank(crtc);
|
|
}
|
|
|
|
static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
spin_lock_irq(&crtc->dev->event_lock);
|
|
if (crtc->state->event) {
|
|
if (drm_crtc_vblank_get(crtc) == 0)
|
|
drm_crtc_arm_vblank_event(crtc, crtc->state->event);
|
|
else
|
|
drm_crtc_send_vblank_event(crtc, crtc->state->event);
|
|
crtc->state->event = NULL;
|
|
}
|
|
spin_unlock_irq(&crtc->dev->event_lock);
|
|
}
|
|
|
|
static bool lsdc_crtc_get_scanout_position(struct drm_crtc *crtc,
|
|
bool in_vblank_irq,
|
|
int *vpos,
|
|
int *hpos,
|
|
ktime_t *stime,
|
|
ktime_t *etime,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops;
|
|
int vsw, vbp, vactive_start, vactive_end, vfp_end;
|
|
int x, y;
|
|
|
|
vsw = mode->crtc_vsync_end - mode->crtc_vsync_start;
|
|
vbp = mode->crtc_vtotal - mode->crtc_vsync_end;
|
|
|
|
vactive_start = vsw + vbp + 1;
|
|
vactive_end = vactive_start + mode->crtc_vdisplay;
|
|
|
|
/* last scan line before VSYNC */
|
|
vfp_end = mode->crtc_vtotal;
|
|
|
|
if (stime)
|
|
*stime = ktime_get();
|
|
|
|
ops->get_scan_pos(lcrtc, &x, &y);
|
|
|
|
if (y > vactive_end)
|
|
y = y - vfp_end - vactive_start;
|
|
else
|
|
y -= vactive_start;
|
|
|
|
*vpos = y;
|
|
*hpos = 0;
|
|
|
|
if (etime)
|
|
*etime = ktime_get();
|
|
|
|
return true;
|
|
}
|
|
|
|
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_check = lsdc_crtc_helper_atomic_check,
|
|
.atomic_flush = lsdc_crtc_atomic_flush,
|
|
.get_scanout_position = lsdc_crtc_get_scanout_position,
|
|
};
|
|
|
|
int ls7a1000_crtc_init(struct drm_device *ddev,
|
|
struct drm_crtc *crtc,
|
|
struct drm_plane *primary,
|
|
struct drm_plane *cursor,
|
|
unsigned int index,
|
|
bool has_vblank)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
int ret;
|
|
|
|
ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index);
|
|
if (ret) {
|
|
drm_err(ddev, "pixel pll init failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
lcrtc->ldev = to_lsdc(ddev);
|
|
lcrtc->has_vblank = has_vblank;
|
|
lcrtc->hw_ops = &ls7a1000_crtc_hw_ops[index];
|
|
|
|
ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor,
|
|
&ls7a1000_crtc_funcs,
|
|
"LS-CRTC-%d", index);
|
|
if (ret) {
|
|
drm_err(ddev, "crtc init with planes failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs);
|
|
|
|
ret = drm_mode_crtc_set_gamma_size(crtc, 256);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_crtc_enable_color_mgmt(crtc, 0, false, 256);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ls7a2000_crtc_init(struct drm_device *ddev,
|
|
struct drm_crtc *crtc,
|
|
struct drm_plane *primary,
|
|
struct drm_plane *cursor,
|
|
unsigned int index,
|
|
bool has_vblank)
|
|
{
|
|
struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc);
|
|
int ret;
|
|
|
|
ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index);
|
|
if (ret) {
|
|
drm_err(ddev, "crtc init with pll failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
lcrtc->ldev = to_lsdc(ddev);
|
|
lcrtc->has_vblank = has_vblank;
|
|
lcrtc->hw_ops = &ls7a2000_crtc_hw_ops[index];
|
|
|
|
ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor,
|
|
&ls7a2000_crtc_funcs,
|
|
"LS-CRTC-%u", index);
|
|
if (ret) {
|
|
drm_err(ddev, "crtc init with planes failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs);
|
|
|
|
ret = drm_mode_crtc_set_gamma_size(crtc, 256);
|
|
if (ret)
|
|
return ret;
|
|
|
|
drm_crtc_enable_color_mgmt(crtc, 0, false, 256);
|
|
|
|
return 0;
|
|
}
|