// 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;