提交 80764bc7 编写于 作者: H Huacai Chen 提交者: Hongchen Zhang

drm/loongson: add kernel modesetting driver support for ls7a1000/ls7a2000

LoongArch inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I6BWFP

--------------------------------

 1) Support Double Screen HW Cursor
 2) Support LS7A1000/LS7A2000 + 3A5000
 3) CRTC's DMA Step is remain 256 bytes

Change-Id: Id3c3a7bebf1e95dcc882f69ba20ff3b7e57d275d
Signed-off-by: NJingfeng Sui <suijingfeng@loongson.cn>
Signed-off-by: NHuacai Chen <chenhuacai@loongson.cn>
上级 ffe6c674
......@@ -594,6 +594,7 @@ CONFIG_DRM_AMDGPU_SI=y
CONFIG_DRM_AMDGPU_CIK=y
CONFIG_DRM_AMDGPU_USERPTR=y
CONFIG_DRM_AST=y
CONFIG_DRM_LOONGSON=y
CONFIG_DRM_QXL=m
CONFIG_DRM_VIRTIO_GPU=m
CONFIG_FB_EFI=y
......
......@@ -312,6 +312,8 @@ source "drivers/gpu/drm/udl/Kconfig"
source "drivers/gpu/drm/ast/Kconfig"
source "drivers/gpu/drm/loongson/Kconfig"
source "drivers/gpu/drm/mgag200/Kconfig"
source "drivers/gpu/drm/armada/Kconfig"
......
......@@ -86,6 +86,7 @@ obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
obj-$(CONFIG_DRM_AST) += ast/
obj-$(CONFIG_DRM_LOONGSON) +=loongson/
obj-$(CONFIG_DRM_ARMADA) += armada/
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
obj-y += rcar-du/
......
# SPDX-License-Identifier: GPL-2.0
config DRM_LOONGSON
tristate "DRM Support for loongson's display controller"
depends on DRM && PCI
depends on MACH_LOONGSON64 || LOONGARCH || MIPS || COMPILE_TEST
select OF
select CMA if HAVE_DMA_CONTIGUOUS
select DMA_CMA if HAVE_DMA_CONTIGUOUS
select DRM_KMS_HELPER
select DRM_KMS_FB_HELPER
select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER
select DRM_TTM
select DRM_TTM_HELPER
select DRM_VRAM_HELPER
select VIDEOMODE_HELPERS
select DRM_BRIDGE
select DRM_PANEL_BRIDGE
default y
help
This is a KMS driver for the display controller in the LS7A1000
bridge chip and LS2K1000/LS2K0500 SoC.
If "M" is selected, the module will be called loongson.
If in doubt, say "Y".
# SPDX-License-Identifier: GPL-2.0
loongson-y := \
lsdc_drv.o \
lsdc_crtc.o \
lsdc_irq.o \
lsdc_plane.o \
lsdc_pll.o \
lsdc_i2c.o \
lsdc_output.o \
lsdc_pci_drv.o \
lsdc_debugfs.o \
lsdc-$(CONFIG_DEBUG_FS) += lsdc_debugfs.o
obj-$(CONFIG_DRM_LOONGSON) += loongson.o
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_vblank.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include "lsdc_drv.h"
#include "lsdc_regs.h"
#include "lsdc_pll.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);
struct drm_crtc_state *state = crtc->state;
u32 val;
if (state->enable) {
val = readl(ldev->reg_base + LSDC_INT_REG);
if (index == 0)
val |= INT_CRTC0_VS_EN;
else if (index == 1)
val |= INT_CRTC1_VS_EN;
writel(val, ldev->reg_base + LSDC_INT_REG);
}
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);
u32 val;
val = readl(ldev->reg_base + LSDC_INT_REG);
if (index == 0)
val &= ~INT_CRTC0_VS_EN;
else if (index == 1)
val &= ~INT_CRTC1_VS_EN;
writel(val, ldev->reg_base + LSDC_INT_REG);
}
static void lsdc_crtc_reset(struct drm_crtc *crtc)
{
struct drm_device *ddev = crtc->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
unsigned int index = drm_crtc_index(crtc);
struct lsdc_crtc_state *priv_crtc_state;
u32 val = CFG_RESET_BIT | CFG_OUTPUT_EN_BIT | LSDC_PF_XRGB8888;
if (ldev->enable_gamma)
val |= CFG_GAMMAR_EN_BIT;
/* align to 64 */
if (ldev->desc->chip == LSDC_CHIP_7A2000) {
val &= ~LS7A2000_DMA_STEP_MASK;
val |= DMA_STEP_256_BYTE;
}
if (index == 0)
writel(val, ldev->reg_base + LSDC_CRTC0_CFG_REG);
else if (index == 1)
writel(val, ldev->reg_base + LSDC_CRTC1_CFG_REG);
if (crtc->state) {
priv_crtc_state = to_lsdc_crtc_state(crtc->state);
__drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base);
kfree(priv_crtc_state);
}
priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL);
if (!priv_crtc_state)
return;
__drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base);
drm_dbg(ddev, "crtc%u reset\n", index);
}
static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state)
{
struct lsdc_crtc_state *priv_crtc_state = to_lsdc_crtc_state(state);
__drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base);
kfree(priv_crtc_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;
struct drm_device *ddev = crtc->dev;
if (drm_WARN_ON(ddev, !crtc->state))
return NULL;
new_priv_state = kmalloc(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->pparams, &old_priv_state->pparams, sizeof(new_priv_state->pparams));
return &new_priv_state->base;
}
static const struct drm_crtc_funcs lsdc_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,
.enable_vblank = lsdc_crtc_enable_vblank,
.disable_vblank = lsdc_crtc_disable_vblank,
};
static enum drm_mode_status
lsdc_crtc_helper_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_chip_desc * const descp = ldev->desc;
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(ddev, "mode %dx%d, pixel clock=%d is too high\n",
mode->hdisplay, mode->vdisplay, mode->clock);
return MODE_CLOCK_HIGH;
}
/* The CRTC hardware dma take 256 bytes once a time,
* this is a limitation of the CRTC.
* TODO: check RGB565 support
*/
if (!ldev->relax_alignment) {
if ((mode->hdisplay * 4) % descp->stride_alignment) {
drm_dbg(ddev, "mode %dx%d, stride is not %u bytes aligned\n",
mode->hdisplay, mode->vdisplay, descp->stride_alignment);
return MODE_BAD;
}
}
return MODE_OK;
}
static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct lsdc_display_pipe * const dispipe = drm_crtc_to_dispipe(crtc);
struct lsdc_pll * const pixpll = &dispipe->pixpll;
const struct lsdc_pixpll_funcs * const pfuncs = pixpll->funcs;
struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state);
bool ret;
ret = pfuncs->compute(pixpll, state->mode.clock, &priv_state->pparams);
if (ret)
return 0;
drm_warn(crtc->dev, "failed find PLL parameters for %u\n", state->mode.clock);
return -EINVAL;
}
static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
if (!state->enable)
return 0; /* no mode checks if CRTC is being disabled */
if (state->mode_changed || state->active_changed || state->connectors_changed)
return lsdc_pixpll_atomic_check(crtc, state);
return 0;
}
static void lsdc_update_pixclk(struct drm_crtc *crtc)
{
struct lsdc_display_pipe * const dispipe = drm_crtc_to_dispipe(crtc);
struct lsdc_pll * const pixpll = &dispipe->pixpll;
const struct lsdc_pixpll_funcs * const clkfun = pixpll->funcs;
struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(crtc->state);
clkfun->update(pixpll, &priv_state->pparams);
}
static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc)
{
struct drm_device *ddev = crtc->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
unsigned int index = drm_crtc_index(crtc);
u32 h_sync, v_sync, h_val, v_val;
/* 26:16 total pixels, 10:0 visiable pixels, in horizontal */
h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay;
/* Hack to support non 256 bytes aligned stride, for example:
* 800x480 DPI panel. In this case userspace do the work to
* guarantee the horizontal pixel size is aligned by padding it.
* In actual, We allocate 832x480x4 bytes in size.
*/
if (ldev->relax_alignment)
h_val = (h_val + 63) & ~63;
/* 26:16 total pixels, 10:0 visiable pixels, in vertical */
v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay;
/* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */
h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | EN_HSYNC_BIT;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
h_sync |= INV_HSYNC_BIT;
/* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */
v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | EN_VSYNC_BIT;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
v_sync |= INV_VSYNC_BIT;
if (index == 0) {
writel(0, ldev->reg_base + LSDC_CRTC0_FB_ORIGIN_REG);
writel(h_val, ldev->reg_base + LSDC_CRTC0_HDISPLAY_REG);
writel(v_val, ldev->reg_base + LSDC_CRTC0_VDISPLAY_REG);
writel(h_sync, ldev->reg_base + LSDC_CRTC0_HSYNC_REG);
writel(v_sync, ldev->reg_base + LSDC_CRTC0_VSYNC_REG);
} else if (index == 1) {
writel(0, ldev->reg_base + LSDC_CRTC1_FB_ORIGIN_REG);
writel(h_val, ldev->reg_base + LSDC_CRTC1_HDISPLAY_REG);
writel(v_val, ldev->reg_base + LSDC_CRTC1_VDISPLAY_REG);
writel(h_sync, ldev->reg_base + LSDC_CRTC1_HSYNC_REG);
writel(v_sync, ldev->reg_base + LSDC_CRTC1_VSYNC_REG);
}
drm_dbg(ddev, "%s modeset: %ux%u\n", crtc->name, mode->hdisplay, mode->vdisplay);
lsdc_update_pixclk(crtc);
}
static void lsdc_enable_display(struct lsdc_device *ldev, unsigned int index)
{
u32 val;
if (index == 0) {
val = readl(ldev->reg_base + LSDC_CRTC0_CFG_REG);
val |= CFG_OUTPUT_EN_BIT;
writel(val, ldev->reg_base + LSDC_CRTC0_CFG_REG);
} else if (index == 1) {
val = readl(ldev->reg_base + LSDC_CRTC1_CFG_REG);
val |= CFG_OUTPUT_EN_BIT;
writel(val, ldev->reg_base + LSDC_CRTC1_CFG_REG);
}
}
static void lsdc_disable_display(struct lsdc_device *ldev, unsigned int index)
{
u32 val;
if (index == 0) {
val = readl(ldev->reg_base + LSDC_CRTC0_CFG_REG);
val &= ~CFG_OUTPUT_EN_BIT;
writel(val, ldev->reg_base + LSDC_CRTC0_CFG_REG);
} else if (index == 1) {
val = readl(ldev->reg_base + LSDC_CRTC1_CFG_REG);
val &= ~CFG_OUTPUT_EN_BIT;
writel(val, ldev->reg_base + LSDC_CRTC1_CFG_REG);
}
}
/*
* @lsdc_crtc_helper_atomic_enable:
*
* This callback should be used to enable the CRTC. With the atomic
* drivers it is called before all encoders connected to this CRTC are
* enabled through the encoder's own &drm_encoder_helper_funcs.enable
* hook. If that sequence is too simple drivers can just add their own
* hooks and call it from this CRTC callback here by looping over all
* encoders connected to it using for_each_encoder_on_crtc().
*
* This hook is used only by atomic helpers, for symmetry with
* @atomic_disable. Atomic drivers don't need to implement it if there's
* no need to enable anything at the CRTC level. To ensure that runtime
* PM handling (using either DPMS or the new "ACTIVE" property) works
* @atomic_enable must be the inverse of @atomic_disable for atomic
* drivers.
*
* Drivers can use the @old_crtc_state input parameter if the operations
* needed to enable the CRTC don't depend solely on the new state but
* also on the transition between the old state and the new state.
*
* This function is optional.
*/
static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct drm_device *ddev = crtc->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
drm_crtc_vblank_on(crtc);
lsdc_enable_display(ldev, drm_crtc_index(crtc));
drm_dbg(ddev, "%s: enabled\n", crtc->name);
}
static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct drm_device *ddev = crtc->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
drm_crtc_vblank_off(crtc);
lsdc_disable_display(ldev, drm_crtc_index(crtc));
drm_dbg(ddev, "%s: disabled\n", crtc->name);
}
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;
writel(0, ldev->reg_base + LSDC_CRTC0_GAMMA_INDEX_REG);
for (i = 0; i < 256; i++) {
u32 val = ((lut->red << 8) & 0xff0000) |
(lut->green & 0xff00) |
(lut->blue >> 8);
if (index == 0)
writel(val, ldev->reg_base + LSDC_CRTC0_GAMMA_DATA_REG);
else if (index == 1)
writel(val, ldev->reg_base + LSDC_CRTC1_GAMMA_DATA_REG);
lut++;
}
}
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);
}
}
static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = {
.mode_valid = lsdc_crtc_helper_mode_valid,
.mode_set_nofb = lsdc_crtc_helper_mode_set_nofb,
.atomic_enable = lsdc_crtc_helper_atomic_enable,
.atomic_disable = lsdc_crtc_helper_atomic_disable,
.atomic_check = lsdc_crtc_helper_atomic_check,
.atomic_flush = lsdc_crtc_atomic_flush,
};
int lsdc_crtc_init(struct drm_device *ddev,
struct drm_crtc *crtc,
unsigned int index,
struct drm_plane *primary,
struct drm_plane *cursor)
{
int ret;
ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor,
&lsdc_crtc_funcs, "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)
drm_warn(ddev, "set the gamma table size failed\n");
drm_crtc_enable_color_mgmt(crtc, 0, false, 256);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_file.h>
#include <drm/drm_device.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_vma_manager.h>
#include <drm/drm_gem_vram_helper.h>
#include "lsdc_drv.h"
#include "lsdc_pll.h"
#include "lsdc_regs.h"
#include "lsdc_debugfs.h"
#ifdef CONFIG_DEBUG_FS
static int lsdc_show_clock(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct drm_device *ddev = node->minor->dev;
struct drm_crtc *crtc;
drm_for_each_crtc(crtc, ddev) {
struct lsdc_display_pipe *pipe;
struct lsdc_pll *pixpll;
const struct lsdc_pixpll_funcs *funcs;
struct lsdc_pll_core_values params;
unsigned int out_khz;
struct drm_display_mode *adj;
pipe = container_of(crtc, struct lsdc_display_pipe, crtc);
if (!pipe->available)
continue;
adj = &crtc->state->adjusted_mode;
pixpll = &pipe->pixpll;
funcs = pixpll->funcs;
out_khz = funcs->get_clock_rate(pixpll, &params);
seq_printf(m, "Display pipe %u: %dx%d\n",
pipe->index, adj->hdisplay, adj->vdisplay);
seq_printf(m, "Frequency actually output: %u kHz\n", out_khz);
seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock);
seq_printf(m, "diff: %d kHz\n", adj->clock);
seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n",
params.div_ref, params.loopc, params.div_out);
seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n",
adj->hsync_start, adj->hsync_end, adj->htotal);
seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n",
adj->vsync_start, adj->vsync_end, adj->vtotal);
}
return 0;
}
static int lsdc_show_mm(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct drm_device *ddev = node->minor->dev;
struct drm_printer p = drm_seq_file_printer(m);
drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p);
return 0;
}
#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), LSDC_##reg##_REG }
static const struct {
const char *name;
u32 reg_offset;
} lsdc_regs_array[] = {
REGDEF(INT),
REGDEF(CRTC0_CFG),
REGDEF(CRTC0_FB_ADDR0),
REGDEF(CRTC0_FB_ADDR1),
REGDEF(CRTC0_FB_HI_ADDR),
REGDEF(CRTC0_STRIDE),
REGDEF(CRTC0_FB_ORIGIN),
REGDEF(CRTC0_HDISPLAY),
REGDEF(CRTC0_HSYNC),
REGDEF(CRTC0_VDISPLAY),
REGDEF(CRTC0_VSYNC),
REGDEF(CRTC0_GAMMA_INDEX),
REGDEF(CRTC0_GAMMA_DATA),
REGDEF(CRTC1_CFG),
REGDEF(CRTC1_FB_ADDR0),
REGDEF(CRTC1_FB_ADDR1),
REGDEF(CRTC1_FB_HI_ADDR),
REGDEF(CRTC1_STRIDE),
REGDEF(CRTC1_FB_ORIGIN),
REGDEF(CRTC1_HDISPLAY),
REGDEF(CRTC1_HSYNC),
REGDEF(CRTC1_VDISPLAY),
REGDEF(CRTC1_VSYNC),
REGDEF(CRTC1_GAMMA_INDEX),
REGDEF(CRTC1_GAMMA_DATA),
REGDEF(CURSOR0_CFG),
REGDEF(CURSOR0_ADDR),
REGDEF(CURSOR0_POSITION),
REGDEF(CURSOR0_BG_COLOR),
REGDEF(CURSOR0_FG_COLOR),
};
static int lsdc_show_regs(struct seq_file *m, void *arg)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct drm_device *ddev = node->minor->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
int i;
for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) {
u32 offset = lsdc_regs_array[i].reg_offset;
const char *name = lsdc_regs_array[i].name;
seq_printf(m, "%s (0x%04x): 0x%08x\n",
name, offset,
readl(ldev->reg_base + offset));
}
return 0;
}
static const struct drm_info_list lsdc_debugfs_list[] = {
{ "clocks", lsdc_show_clock, 0 },
{ "mm", lsdc_show_mm, 0, NULL },
{ "regs", lsdc_show_regs, 0 },
};
void lsdc_debugfs_init(struct drm_minor *minor)
{
drm_debugfs_create_files(lsdc_debugfs_list,
ARRAY_SIZE(lsdc_debugfs_list),
minor->debugfs_root,
minor);
}
/*
* vram debugfs related.
*/
static int lsdc_vram_mm_show(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct drm_vram_mm *vmm = node->minor->dev->vram_mm;
struct ttm_resource_manager *man = ttm_manager_type(&vmm->bdev, TTM_PL_VRAM);
struct drm_printer p = drm_seq_file_printer(m);
ttm_resource_manager_debug(man, &p);
return 0;
}
static const struct drm_info_list lsdc_vram_mm_debugfs_list[] = {
{ "clocks", lsdc_show_clock, 0 },
{ "vram-mm", lsdc_vram_mm_show, 0, NULL },
{ "regs", lsdc_show_regs, 0 },
};
void lsdc_vram_mm_debugfs_init(struct drm_minor *minor)
{
drm_debugfs_create_files(lsdc_vram_mm_debugfs_list,
ARRAY_SIZE(lsdc_vram_mm_debugfs_list),
minor->debugfs_root,
minor);
}
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_DEBUGFS_H__
#define __LSDC_DEBUGFS_H__
void lsdc_debugfs_init(struct drm_minor *minor);
void lsdc_vram_mm_debugfs_init(struct drm_minor *minor);
#endif
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <linux/pci.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <drm/drm_vblank.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_vram_helper.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_probe_helper.h>
#include "lsdc_drv.h"
#include "lsdc_irq.h"
#include "lsdc_output.h"
#include "lsdc_debugfs.h"
static const struct lsdc_chip_desc dc_in_ls2k1000 = {
.chip = LSDC_CHIP_2K1000,
.num_of_crtc = LSDC_NUM_CRTC,
/* ls2k1000 user manual say the max pixel clock can be about 200MHz */
.max_pixel_clk = 200000,
.max_width = 2560,
.max_height = 2048,
.num_of_hw_cursor = 1,
.hw_cursor_w = 32,
.hw_cursor_h = 32,
.stride_alignment = 256,
.has_builtin_i2c = false,
.has_vram = false,
.broken_gamma = true,
};
static const struct lsdc_chip_desc dc_in_ls2k0500 = {
.chip = LSDC_CHIP_2K0500,
.num_of_crtc = LSDC_NUM_CRTC,
.max_pixel_clk = 200000,
.max_width = 2048,
.max_height = 2048,
.num_of_hw_cursor = 1,
.hw_cursor_w = 32,
.hw_cursor_h = 32,
.stride_alignment = 256,
.has_builtin_i2c = false,
.has_vram = false,
.broken_gamma = true,
};
static const struct lsdc_chip_desc dc_in_ls7a1000 = {
.chip = LSDC_CHIP_7A1000,
.num_of_crtc = LSDC_NUM_CRTC,
.max_pixel_clk = 200000,
.max_width = 2048,
.max_height = 2048,
.num_of_hw_cursor = 1,
.hw_cursor_w = 32,
.hw_cursor_h = 32,
.stride_alignment = 256,
.has_builtin_i2c = true,
.has_vram = true,
.broken_gamma = true,
};
static const struct lsdc_chip_desc dc_in_ls7a2000 = {
.chip = LSDC_CHIP_7A2000,
.num_of_crtc = LSDC_NUM_CRTC,
.max_pixel_clk = 200000,
.max_width = 2048,
.max_height = 2048,
.num_of_hw_cursor = 2,
.hw_cursor_w = 64,
.hw_cursor_h = 64,
.stride_alignment = 256,
.has_builtin_i2c = true,
.has_vram = true,
.broken_gamma = true,
};
static enum drm_mode_status
lsdc_device_mode_valid(struct drm_device *ddev, const struct drm_display_mode *mode)
{
struct lsdc_device *ldev = to_lsdc(ddev);
if (ldev->use_vram_helper)
return drm_vram_helper_mode_valid(ddev, mode);
return MODE_OK;
}
static const struct drm_mode_config_funcs lsdc_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
.mode_valid = lsdc_device_mode_valid,
};
static int lsdc_gem_cma_dumb_create(struct drm_file *file,
struct drm_device *ddev,
struct drm_mode_create_dumb *args)
{
struct lsdc_device *ldev = to_lsdc(ddev);
const struct lsdc_chip_desc *desc = ldev->desc;
unsigned int bytes_per_pixel = (args->bpp + 7) / 8;
unsigned int pitch = bytes_per_pixel * args->width;
/*
* The dc in ls7a1000/ls2k1000/ls2k0500 require the stride be a
* multiple of 256 bytes which is for sake of optimize dma data
* transfer.
*/
args->pitch = roundup(pitch, desc->stride_alignment);
return drm_gem_cma_dumb_create_internal(file, ddev, args);
}
DEFINE_DRM_GEM_CMA_FOPS(lsdc_drv_fops);
static struct drm_driver lsdc_drm_driver_cma_stub = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.lastclose = drm_fb_helper_lastclose,
.fops = &lsdc_drv_fops,
.name = "lsdc",
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(lsdc_gem_cma_dumb_create),
#ifdef CONFIG_DEBUG_FS
.debugfs_init = lsdc_debugfs_init,
#endif
};
DEFINE_DRM_GEM_FOPS(lsdc_gem_fops);
static struct drm_driver lsdc_vram_driver_stub = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
.fops = &lsdc_gem_fops,
.name = "loongson-drm",
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
#ifdef CONFIG_DEBUG_FS
.debugfs_init = lsdc_vram_mm_debugfs_init,
#endif
.dumb_create = drm_gem_vram_driver_dumb_create,
.dumb_map_offset = drm_gem_vram_driver_dumb_mmap_offset,
.gem_prime_mmap = drm_gem_prime_mmap,
};
static int lsdc_modeset_init(struct lsdc_device *ldev, uint32_t num_crtc)
{
struct drm_device *ddev = ldev->ddev;
unsigned int i;
int ret;
if (ldev->has_ports_node) {
drm_info(ddev, "Has OF graph support\n");
ret = lsdc_attach_output(ldev, num_crtc);
if (ret)
return ret;
} else {
drm_info(ddev, "No OF graph support\n");
for (i = 0; i < num_crtc; i++) {
ret = lsdc_create_output(ldev, i, num_crtc);
if (ret)
return ret;
}
}
for (i = 0; i < num_crtc; i++) {
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[i];
struct drm_plane * const primary = &dispipe->primary;
struct drm_plane * const cursor = &dispipe->cursor;
struct drm_crtc * const crtc = &dispipe->crtc;
struct lsdc_pll * const pixpll = &dispipe->pixpll;
dispipe->index = i;
ret = lsdc_pixpll_init(pixpll, ddev, i);
if (ret)
return ret;
ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, i);
if (ret)
return ret;
ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i);
if (ret)
return ret;
/*
* Initial all of the CRTC available, in this way the crtc
* index is equal to the hardware crtc index. That is:
* display pipe 0 => crtc0 + dvo0 + encoder0
* display pipe 1 => crtc1 + dvo1 + encoder1
*/
ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor);
if (ret)
return ret;
drm_info(ddev, "display pipe %u initialized\n", i);
}
return 0;
}
static int lsdc_mode_config_init(struct lsdc_device *ldev)
{
const struct lsdc_chip_desc * const descp = ldev->desc;
struct drm_device *ddev = ldev->ddev;
int ret;
ret = drmm_mode_config_init(ddev);
if (ret)
return ret;
ddev->mode_config.funcs = &lsdc_mode_config_funcs;
ddev->mode_config.min_width = 1;
ddev->mode_config.min_height = 1;
ddev->mode_config.max_width = 4096;
ddev->mode_config.max_height = 4096;
ddev->mode_config.preferred_depth = 24;
ddev->mode_config.prefer_shadow = ldev->use_vram_helper;
ddev->mode_config.cursor_width = descp->hw_cursor_h;
ddev->mode_config.cursor_height = descp->hw_cursor_h;
if (ldev->vram_base)
ddev->mode_config.fb_base = ldev->vram_base;
return lsdc_modeset_init(ldev, descp->num_of_crtc);
}
static void lsdc_mode_config_fini(struct drm_device *ddev)
{
drm_atomic_helper_shutdown(ddev);
drm_mode_config_cleanup(ddev);
}
/*
* lsdc_detect_chip - a function to tell different chips apart.
*/
const struct lsdc_chip_desc *
lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id * const ent)
{
static const struct lsdc_match {
char name[128];
const void *data;
} compat[] = {
{ .name = "loongson,ls7a1000-dc", .data = &dc_in_ls7a1000 },
{ .name = "loongson,ls2k1000-dc", .data = &dc_in_ls2k1000 },
{ .name = "loongson,ls2k0500-dc", .data = &dc_in_ls2k0500 },
{ .name = "loongson,ls7a2000-dc", .data = &dc_in_ls7a2000 },
{ .name = "loongson,loongson64c-4core-ls7a", .data = &dc_in_ls7a1000 },
{ .name = "loongson,loongson64g-4core-ls7a", .data = &dc_in_ls7a1000 },
{ .name = "loongson,loongson64-2core-2k1000", .data = &dc_in_ls2k1000 },
{ .name = "loongson,loongson2k1000", .data = &dc_in_ls2k1000 },
{ /* sentinel */ },
};
struct device_node *np;
unsigned int i;
if (ent->driver_data == LSDC_CHIP_7A2000)
return &dc_in_ls7a2000;
if (ent->driver_data == LSDC_CHIP_7A1000)
return &dc_in_ls7a1000;
/* Deduce DC variant information from the DC device node */
for (i = 0; i < ARRAY_SIZE(compat); ++i) {
np = of_find_compatible_node(NULL, NULL, compat[i].name);
if (!np)
continue;
of_node_put(np);
return compat[i].data;
}
dev_info(&pdev->dev, "No Compatible Device Node Found\n");
if (pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL))
return &dc_in_ls7a1000;
else if (pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A05, NULL))
return &dc_in_ls2k1000;
return NULL;
}
static int lsdc_remove_conflicting_framebuffers(const struct drm_driver *drv)
{
struct apertures_struct *ap;
ap = alloc_apertures(1);
if (!ap)
return -ENOMEM;
/* lsdc is a pci device, but it don't have a dedicate vram bar because
* of historic reason. The display controller is ported from Loongson
* 2H series SoC which date back to 2012.
* And simplefb node may have been located anywhere in memory.
*/
ap->ranges[0].base = 0;
ap->ranges[0].size = ~0;
return drm_fb_helper_remove_conflicting_framebuffers(ap, "loongsondrmfb", false);
}
static int lsdc_platform_probe(struct platform_device *pdev)
{
struct lsdc_device *ldev = dev_get_drvdata(pdev->dev.parent);
struct drm_driver *driver;
struct drm_device *ddev;
int ret;
if (ldev->use_vram_helper)
driver = &lsdc_vram_driver_stub;
else
driver = &lsdc_drm_driver_cma_stub;
lsdc_remove_conflicting_framebuffers(driver);
ddev = drm_dev_alloc(driver, &pdev->dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
platform_set_drvdata(pdev, ddev);
ldev->ddev = ddev;
ddev->dev_private = ldev;
if (ldev->use_vram_helper) {
ret = drmm_vram_helper_init(ddev, ldev->vram_base, ldev->vram_size);
if (ret) {
drm_err(ddev, "vram helper init failed: %d\n", ret);
goto err_kms;
}
};
ret = lsdc_mode_config_init(ldev);
if (ret) {
drm_dbg(ddev, "%s: %d\n", __func__, ret);
goto err_kms;
}
ret = devm_request_threaded_irq(&pdev->dev, ldev->irq,
lsdc_irq_handler_cb,
lsdc_irq_thread_cb,
IRQF_ONESHOT, NULL,
ddev);
if (ret) {
drm_err(ddev, "Failed to register lsdc interrupt\n");
goto err_kms;
}
ret = drm_vblank_init(ddev, ldev->desc->num_of_crtc);
if (ret)
goto err_kms;
drm_mode_config_reset(ddev);
drm_kms_helper_poll_init(ddev);
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_poll_fini;
drm_fbdev_generic_setup(ddev, 32);
return 0;
err_poll_fini:
drm_kms_helper_poll_fini(ddev);
err_kms:
drm_dev_put(ddev);
return ret;
}
static int lsdc_platform_remove(struct platform_device *pdev)
{
struct drm_device *ddev = platform_get_drvdata(pdev);
struct lsdc_device *ldev = to_lsdc(ddev);
drm_dev_unregister(ddev);
drm_kms_helper_poll_fini(ddev);
devm_free_irq(ddev->dev, ldev->irq, ddev);
lsdc_mode_config_fini(ddev);
platform_set_drvdata(pdev, NULL);
drm_dev_put(ddev);
return 0;
}
struct platform_driver lsdc_platform_driver = {
.probe = lsdc_platform_probe,
.remove = lsdc_platform_remove,
.driver = {
.name = "lsdc",
},
};
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_DRV_H__
#define __LSDC_DRV_H__
#include <drm/drm_print.h>
#include <drm/drm_device.h>
#include <drm/drm_crtc.h>
#include <drm/drm_plane.h>
#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
#include <drm/drm_drv.h>
#include <drm/drm_atomic.h>
#include "lsdc_pll.h"
#define DRIVER_AUTHOR "Sui Jingfeng <suijingfeng@loongson.cn>"
#define DRIVER_NAME "lsdc"
#define DRIVER_DESC "drm driver for loongson's display controller"
#define DRIVER_DATE "20200701"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 0
#define DRIVER_PATCHLEVEL 0
#define LSDC_NUM_CRTC 2
enum loongson_dc_family {
LSDC_CHIP_UNKNOWN = 0,
LSDC_CHIP_2K1000 = 1, /* 2-Core Mips64r2 compatible SoC */
LSDC_CHIP_7A1000 = 2, /* North bridge of LS3A3000/LS3A4000/LS3A5000 */
LSDC_CHIP_2K0500 = 3, /* Single core, reduced version of LS2K1000 */
LSDC_CHIP_7A2000 = 4, /* Enhancement version of LS7a1000 */
LSDC_CHIP_LAST,
};
struct lsdc_chip_desc {
enum loongson_dc_family chip;
u32 num_of_crtc;
u32 max_pixel_clk;
u32 max_width;
u32 max_height;
u32 num_of_hw_cursor;
u32 hw_cursor_w;
u32 hw_cursor_h;
/* DMA alignment constraint (must be multiple of 256 bytes) */
u32 stride_alignment;
bool has_builtin_i2c;
bool has_vram;
bool broken_gamma;
};
/* There is only a 1:1 mapping of encoders and connectors for lsdc */
struct lsdc_output {
struct drm_encoder encoder;
struct drm_connector connector;
struct lsdc_i2c *li2c;
};
static inline struct lsdc_output *
drm_connector_to_lsdc_output(struct drm_connector *connp)
{
return container_of(connp, struct lsdc_output, connector);
}
/*
* struct lsdc_display_pipe - Abstraction of hardware display pipeline.
* @crtc: CRTC control structure
* @plane: Plane control structure
* @encoder: Encoder control structure
* @pixpll: Pll control structure
* @connector: point to connector control structure this display pipe bind
* @index: the index corresponding to the hardware display pipe
* @available: is this display pipe is available on the motherboard, The
* downstream mother board manufacturer may use only one of them.
* For example, LEMOTE LX-6901 board just has only one VGA output.
*
* Display pipeline with planes, crtc, pll and output collapsed into one entity.
*/
struct lsdc_display_pipe {
struct drm_crtc crtc;
struct drm_plane primary;
struct drm_plane cursor;
struct lsdc_pll pixpll;
struct lsdc_output *output;
int index;
bool available;
};
static inline struct lsdc_display_pipe *
drm_crtc_to_dispipe(struct drm_crtc *crtc)
{
return container_of(crtc, struct lsdc_display_pipe, crtc);
}
static inline struct lsdc_display_pipe *
lsdc_cursor_to_dispipe(struct drm_plane *plane)
{
return container_of(plane, struct lsdc_display_pipe, cursor);
}
struct lsdc_crtc_state {
struct drm_crtc_state base;
struct lsdc_pll_core_values pparams;
};
struct lsdc_device {
struct device *dev;
struct drm_device *ddev;
/* @dc: pointer to the platform device created at runtime */
struct platform_device *dc;
/* @desc: device dependent data and feature descriptions */
const struct lsdc_chip_desc *desc;
/* LS7A1000/LS7A2000 has a dediacted video RAM */
void __iomem *reg_base;
void __iomem *vram;
resource_size_t vram_base;
resource_size_t vram_size;
struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC];
/*
* @num_output: count the number of active display pipe.
*/
unsigned int num_output;
int irq;
u32 irq_status;
/*
* @use_vram_helper: using vram helper base solution instead of
* CMA helper based solution. The DC scanout from the VRAM is
* proved to be more reliable, but graphic application is may
* become slow when using this driver mode.
*/
bool use_vram_helper;
/*
* @enable_gamma: control whether hardware gamma support should be
* enabled or not. It is broken though, but you can know that only
* when you can enable it.
*/
bool enable_gamma;
/* @relax_alignment: for 800x480, 1366x768 resulotion support */
bool relax_alignment;
/* @has_dt: true if there are DT support*/
bool has_dt;
/* @has_ports_node: true if there are OF graph in the DT */
bool has_ports_node;
};
static inline struct lsdc_device *to_lsdc(struct drm_device *ddev)
{
return ddev->dev_private;
}
static inline struct lsdc_crtc_state *
to_lsdc_crtc_state(struct drm_crtc_state *base)
{
return container_of(base, struct lsdc_crtc_state, base);
}
int lsdc_crtc_init(struct drm_device *ddev,
struct drm_crtc *crtc,
unsigned int index,
struct drm_plane *primary,
struct drm_plane *cursor);
int lsdc_plane_init(struct lsdc_device *ldev, struct drm_plane *plane,
enum drm_plane_type type, unsigned int index);
const struct lsdc_chip_desc *
lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id * const ent);
extern struct platform_driver lsdc_platform_driver;
#endif
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <linux/i2c.h>
#include <drm/drm_print.h>
#include <drm/drm_device.h>
#include <drm/drm_managed.h>
#include "lsdc_regs.h"
#include "lsdc_i2c.h"
/*
* ls7a_gpio_i2c_set - set the state of a gpio pin, either high or low.
* @mask: gpio pin mask indicate which pin to set
*/
static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state)
{
unsigned long flags;
u8 val;
spin_lock_irqsave(&li2c->reglock, flags);
if (state) {
/*
* The high state is achieved by setting the direction as
* input, because the GPIO is open drained with external
* pull up resistance.
*/
val = readb(li2c->dir_reg);
val |= mask;
writeb(val, li2c->dir_reg);
} else {
/* First, set this pin as output */
val = readb(li2c->dir_reg);
val &= ~mask;
writeb(val, li2c->dir_reg);
/* Then, set the state to it */
val = readb(li2c->dat_reg);
val &= ~mask;
writeb(val, li2c->dat_reg);
}
spin_unlock_irqrestore(&li2c->reglock, flags);
}
/*
* ls7a_gpio_i2c_get - read value back from gpio pin
* @mask: gpio pin mask indicate which pin to read from
*/
static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask)
{
unsigned long flags;
u8 val;
spin_lock_irqsave(&li2c->reglock, flags);
/* First, set this pin as input */
val = readb(li2c->dir_reg);
val |= mask;
writeb(val, li2c->dir_reg);
/* Then, get level state from this pin */
val = readb(li2c->dat_reg);
spin_unlock_irqrestore(&li2c->reglock, flags);
return (val & mask) ? 1 : 0;
}
/* set the state on the i2c->sda pin */
static void ls7a_i2c_set_sda(void *i2c, int state)
{
struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
return ls7a_gpio_i2c_set(li2c, li2c->sda, state);
}
/* set the state on the i2c->scl pin */
static void ls7a_i2c_set_scl(void *i2c, int state)
{
struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
return ls7a_gpio_i2c_set(li2c, li2c->scl, state);
}
/* read the value from the i2c->sda pin */
static int ls7a_i2c_get_sda(void *i2c)
{
struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
return ls7a_gpio_i2c_get(li2c, li2c->sda);
}
/* read the value from the i2c->scl pin */
static int ls7a_i2c_get_scl(void *i2c)
{
struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
return ls7a_gpio_i2c_get(li2c, li2c->scl);
}
/*
* Mainly for dc in ls7a1000 which have dedicated gpio hardware
*/
static void lsdc_of_release_i2c_adapter(void *res)
{
struct lsdc_i2c *li2c = res;
struct i2c_adapter *adapter;
struct device_node *i2c_np;
adapter = &li2c->adapter;
i2c_np = adapter->dev.of_node;
if (i2c_np)
of_node_put(i2c_np);
i2c_del_adapter(adapter);
kfree(li2c);
}
struct lsdc_i2c *lsdc_of_create_i2c_adapter(struct device *parent,
void *reg_base,
struct device_node *i2c_np)
{
unsigned int udelay = 5;
unsigned int timeout = 2200;
int nr = -1;
struct i2c_adapter *adapter;
struct lsdc_i2c *li2c;
u32 sda, scl;
int ret;
li2c = kzalloc(sizeof(*li2c), GFP_KERNEL);
if (!li2c)
return ERR_PTR(-ENOMEM);
spin_lock_init(&li2c->reglock);
ret = of_property_read_u32(i2c_np, "loongson,sda", &sda);
if (ret) {
dev_err(parent, "No sda pin number provided\n");
return ERR_PTR(ret);
}
ret = of_property_read_u32(i2c_np, "loongson,scl", &scl);
if (ret) {
dev_err(parent, "No scl pin number provided\n");
return ERR_PTR(ret);
}
ret = of_property_read_u32(i2c_np, "loongson,nr", &nr);
if (ret) {
int id;
if (ret == -EINVAL)
dev_dbg(parent, "no nr provided\n");
id = of_alias_get_id(i2c_np, "i2c");
if (id >= 0)
nr = id;
}
li2c->sda = 1 << sda;
li2c->scl = 1 << scl;
/* Optional properties which made the driver more flexible */
of_property_read_u32(i2c_np, "loongson,udelay", &udelay);
of_property_read_u32(i2c_np, "loongson,timeout", &timeout);
li2c->dir_reg = reg_base + LS7A_DC_GPIO_DIR_REG;
li2c->dat_reg = reg_base + LS7A_DC_GPIO_DAT_REG;
li2c->bit.setsda = ls7a_i2c_set_sda;
li2c->bit.setscl = ls7a_i2c_set_scl;
li2c->bit.getsda = ls7a_i2c_get_sda;
li2c->bit.getscl = ls7a_i2c_get_scl;
li2c->bit.udelay = udelay;
li2c->bit.timeout = usecs_to_jiffies(timeout);
li2c->bit.data = li2c;
adapter = &li2c->adapter;
adapter->algo_data = &li2c->bit;
adapter->owner = THIS_MODULE;
adapter->class = I2C_CLASS_DDC;
adapter->dev.parent = parent;
adapter->nr = nr;
adapter->dev.of_node = i2c_np;
snprintf(adapter->name, sizeof(adapter->name), "gpio-i2c-%d", nr);
i2c_set_adapdata(adapter, li2c);
ret = i2c_bit_add_numbered_bus(adapter);
if (ret) {
if (i2c_np)
of_node_put(i2c_np);
kfree(li2c);
return ERR_PTR(ret);
}
dev_info(parent, "sda=%u, scl=%u, nr=%d, udelay=%u, timeout=%u\n",
li2c->sda, li2c->scl, nr, udelay, timeout);
ret = devm_add_action_or_reset(parent, lsdc_of_release_i2c_adapter, li2c);
if (ret)
return NULL;
return li2c;
}
static void lsdc_release_i2c_chan(struct drm_device *dev, void *res)
{
struct lsdc_i2c *li2c = res;
i2c_del_adapter(&li2c->adapter);
kfree(li2c);
}
struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev,
void *reg_base,
unsigned int index)
{
struct i2c_adapter *adapter;
struct lsdc_i2c *li2c;
int ret;
li2c = kzalloc(sizeof(*li2c), GFP_KERNEL);
if (!li2c)
return ERR_PTR(-ENOMEM);
if (index == 0) {
li2c->sda = 0x01;
li2c->scl = 0x02;
} else if (index == 1) {
li2c->sda = 0x04;
li2c->scl = 0x08;
}
spin_lock_init(&li2c->reglock);
li2c->dir_reg = reg_base + LS7A_DC_GPIO_DIR_REG;
li2c->dat_reg = reg_base + LS7A_DC_GPIO_DAT_REG;
li2c->bit.setsda = ls7a_i2c_set_sda;
li2c->bit.setscl = ls7a_i2c_set_scl;
li2c->bit.getsda = ls7a_i2c_get_sda;
li2c->bit.getscl = ls7a_i2c_get_scl;
li2c->bit.udelay = 5;
li2c->bit.timeout = usecs_to_jiffies(2200);
li2c->bit.data = li2c;
adapter = &li2c->adapter;
adapter->algo_data = &li2c->bit;
adapter->owner = THIS_MODULE;
adapter->class = I2C_CLASS_DDC;
adapter->dev.parent = ddev->dev;
adapter->nr = -1;
snprintf(adapter->name, sizeof(adapter->name), "gpio-i2c-%d", index);
i2c_set_adapdata(adapter, li2c);
ret = i2c_bit_add_bus(adapter);
if (ret) {
kfree(li2c);
return ERR_PTR(ret);
}
ret = drmm_add_action_or_reset(ddev, lsdc_release_i2c_chan, li2c);
if (ret)
return NULL;
drm_info(ddev, "%s: sda=%u, scl=%u\n",
adapter->name, li2c->sda, li2c->scl);
return li2c;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_I2C__
#define __LSDC_I2C__
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
#include <linux/of.h>
struct lsdc_i2c {
struct i2c_adapter adapter;
struct i2c_algo_bit_data bit;
/* @reglock: protects concurrent register access */
spinlock_t reglock;
void __iomem *dir_reg;
void __iomem *dat_reg;
/* pin bit mask */
u8 sda;
u8 scl;
};
struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev,
void *reg_base,
unsigned int index);
struct lsdc_i2c *lsdc_of_create_i2c_adapter(struct device *dev,
void *reg_base,
struct device_node *i2c_np);
#endif
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_vblank.h>
#include "lsdc_drv.h"
#include "lsdc_regs.h"
#include "lsdc_irq.h"
/* Function to be called in a threaded interrupt context. */
irqreturn_t lsdc_irq_thread_cb(int irq, void *arg)
{
struct drm_device *ddev = arg;
struct lsdc_device *ldev = to_lsdc(ddev);
struct drm_crtc *crtc;
/* trigger the vblank event */
if (ldev->irq_status & INT_CRTC0_VS) {
crtc = drm_crtc_from_index(ddev, 0);
drm_crtc_handle_vblank(crtc);
}
if (ldev->irq_status & INT_CRTC1_VS) {
crtc = drm_crtc_from_index(ddev, 1);
drm_crtc_handle_vblank(crtc);
}
writel(INT_CRTC0_VS_EN | INT_CRTC1_VS_EN, ldev->reg_base + LSDC_INT_REG);
return IRQ_HANDLED;
}
/* Function to be called when the IRQ occurs */
irqreturn_t lsdc_irq_handler_cb(int irq, void *arg)
{
struct drm_device *ddev = arg;
struct lsdc_device *ldev = to_lsdc(ddev);
/* Read & Clear the interrupt status */
ldev->irq_status = readl(ldev->reg_base + LSDC_INT_REG);
if ((ldev->irq_status & INT_STATUS_MASK) == 0) {
drm_warn(ddev, "no interrupt occurs\n");
return IRQ_NONE;
}
/* clear all interrupt */
writel(ldev->irq_status, ldev->reg_base + LSDC_INT_REG);
return IRQ_WAKE_THREAD;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_IRQ_H__
#define __LSDC_IRQ_H__
irqreturn_t lsdc_irq_thread_cb(int irq, void *arg);
irqreturn_t lsdc_irq_handler_cb(int irq, void *arg);
#endif
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <drm/drm_of.h>
#include <drm/drm_edid.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_connector.h>
#include <drm/drm_bridge_connector.h>
#include "lsdc_drv.h"
#include "lsdc_i2c.h"
#include "lsdc_output.h"
#include "lsdc_regs.h"
static int lsdc_get_modes(struct drm_connector *connector)
{
unsigned int num = 0;
struct lsdc_output *lop = drm_connector_to_lsdc_output(connector);
struct lsdc_i2c *li2c = lop->li2c;
struct i2c_adapter *ddc = &li2c->adapter;
if (ddc) {
struct edid *edid;
edid = drm_get_edid(connector, ddc);
if (edid) {
drm_connector_update_edid_property(connector, edid);
num = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return num;
}
drm_dbg(connector->dev, "Failed to get mode from ddc\n");
num = drm_add_modes_noedid(connector, 1920, 1200);
drm_set_preferred_mode(connector, 1024, 768);
return num;
}
static enum drm_connector_status
lsdc_connector_detect(struct drm_connector *connector, bool force)
{
struct lsdc_output *lop = drm_connector_to_lsdc_output(connector);
struct lsdc_i2c *li2c = lop->li2c;
struct i2c_adapter *ddc = &li2c->adapter;
if (ddc && drm_probe_ddc(ddc))
return connector_status_connected;
if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
return connector_status_connected;
if (connector->connector_type == DRM_MODE_CONNECTOR_DVIA ||
connector->connector_type == DRM_MODE_CONNECTOR_DVID ||
connector->connector_type == DRM_MODE_CONNECTOR_DVII)
return connector_status_disconnected;
if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
return connector_status_disconnected;
return connector_status_unknown;
}
static void lsdc_connector_destroy(struct drm_connector *connector)
{
drm_connector_cleanup(connector);
}
static const struct drm_connector_helper_funcs lsdc_connector_helpers = {
.get_modes = lsdc_get_modes,
};
static const struct drm_connector_funcs lsdc_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = lsdc_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = lsdc_connector_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static enum drm_mode_status
ls7a2000_hdmi_encoder_mode_valid(struct drm_encoder *crtc,
const struct drm_display_mode *mode)
{
return MODE_OK;
}
static void ls7a2000_hdmi_encoder_disable(struct drm_encoder *encoder)
{
int index = encoder->index;
struct lsdc_device *ldev = to_lsdc(encoder->dev);
if (index == 0) {
/* Enable hdmi */
writel(0, ldev->reg_base + HDMI0_CTRL_REG);
} else if (index == 1) {
/* Enable hdmi */
writel(0, ldev->reg_base + HDMI1_CTRL_REG);
}
drm_dbg(encoder->dev, "HDMI%d disable\n", index);
}
static void ls7a2000_hdmi_encoder_enable(struct drm_encoder *encoder)
{
int index = encoder->index;
struct lsdc_device *ldev = to_lsdc(encoder->dev);
if (index == 0) {
/* Enable hdmi */
writel(0x280 | HDMI_EN | HDMI_PACKET_EN, ldev->reg_base + HDMI0_CTRL_REG);
/* hdmi zone idle */
writel(0x00400040, ldev->reg_base + HDMI0_ZONE_REG);
} else if (index == 1) {
/* Enable hdmi */
writel(0x280 | HDMI_EN | HDMI_PACKET_EN, ldev->reg_base + HDMI1_CTRL_REG);
/* hdmi zone idle */
writel(0x00400040, ldev->reg_base + HDMI1_ZONE_REG);
}
drm_dbg(encoder->dev, "HDMI%d enable\n", index);
}
static void
ls7a2000_hdmi_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
int index = encoder->index;
struct drm_device *ddev = encoder->dev;
struct lsdc_device *ldev = to_lsdc(ddev);
int clock = mode->clock;
u32 val;
int counter;
if (index == 0) {
writel(0x0, ldev->reg_base + HDMI0_PLL_REG);
writel(0x0, ldev->reg_base + HDMI0_PHY_CTRL_REG);
} else {
writel(0x0, ldev->reg_base + HDMI1_PLL_REG);
writel(0x0, ldev->reg_base + HDMI1_PHY_CTRL_REG);
}
if (clock >= 170000)
val = (0x0 << 13) | (0x28 << 6) | (0x10 << 1) | HDMI_PLL_EN;
else if (clock >= 85000 && clock < 170000)
val = (0x1 << 13) | (0x28 << 6) | (0x8 << 1) | HDMI_PLL_EN;
else if (clock >= 42500 && clock < 85000)
val = (0x2 << 13) | (0x28 << 6) | (0x4 << 1) | HDMI_PLL_EN;
else if (clock >= 21250 && clock < 42500)
val = (0x3 << 13) | (0x28 << 6) | (0x2 << 1) | HDMI_PLL_EN;
if (index == 0) {
writel(val, ldev->reg_base + HDMI0_PLL_REG);
} else {
writel(val, ldev->reg_base + HDMI1_PLL_REG);
}
do {
/* wait pll lock */
if (index == 0)
val = readl(ldev->reg_base + HDMI0_PLL_REG);
else if (index == 1)
val = readl(ldev->reg_base + HDMI1_PLL_REG);
++counter;
} while (((val & HDMI_PLL_LOCKED) == 0) && (counter < 1000));
drm_dbg(ddev, "HDMI%d modeset, PLL: %u loop waited\n", index, counter);
if (index == 0) {
writel(0x0f03, ldev->reg_base + HDMI0_PHY_CTRL_REG);
} else if (index == 1) {
writel(0x0f03, ldev->reg_base + HDMI1_PHY_CTRL_REG);
}
}
static const struct drm_encoder_helper_funcs ls7a2000_hdmi_encoder_helper_funcs = {
.mode_valid = ls7a2000_hdmi_encoder_mode_valid,
.disable = ls7a2000_hdmi_encoder_disable,
.enable = ls7a2000_hdmi_encoder_enable,
.mode_set = ls7a2000_hdmi_encoder_mode_set,
};
static void lsdc_encoder_reset(struct drm_encoder *encoder)
{
struct lsdc_device *ldev = to_lsdc(encoder->dev);
if (ldev->desc->chip == LSDC_CHIP_7A2000)
ls7a2000_hdmi_encoder_enable(encoder);
}
static const struct drm_encoder_funcs lsdc_encoder_funcs = {
.reset = lsdc_encoder_reset,
.destroy = drm_encoder_cleanup,
};
static int lsdc_attach_bridges(struct lsdc_device *ldev,
struct device_node *ports,
unsigned int i)
{
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[i];
struct drm_device *ddev = ldev->ddev;
struct drm_bridge *bridge;
struct drm_panel *panel;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct lsdc_output *output;
int ret;
ret = drm_of_find_panel_or_bridge(ports, i, 0, &panel, &bridge);
if (panel) {
bridge = devm_drm_panel_bridge_add_typed(ddev->dev, panel, DRM_MODE_CONNECTOR_DPI);
drm_info(ddev, "output-%u is a DPI panel\n", i);
}
if (!bridge)
return ret;
output = devm_kzalloc(ddev->dev, sizeof(*output), GFP_KERNEL);
if (!output)
return -ENOMEM;
encoder = &output->encoder;
ret = drm_encoder_init(ddev, encoder, &lsdc_encoder_funcs,
DRM_MODE_ENCODER_DPI, "encoder-%u", i);
if (ret) {
drm_err(ddev, "Failed to init encoder: %d\n", ret);
return ret;
}
encoder->possible_crtcs = BIT(i);
ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
drm_err(ddev,
"failed to attach bridge %pOF for output %u (%d)\n",
bridge->of_node, i, ret);
return ret;
}
connector = drm_bridge_connector_init(ddev, encoder);
if (IS_ERR(connector)) {
drm_err(ddev, "Unable to init connector\n");
return PTR_ERR(connector);
}
drm_connector_attach_encoder(connector, encoder);
drm_info(ddev, "bridge-%u attached to %s\n", i, encoder->name);
dispipe->output = output;
return 0;
}
int lsdc_attach_output(struct lsdc_device *ldev, uint32_t num_crtc)
{
struct drm_device *ddev = ldev->ddev;
struct device_node *ports;
struct lsdc_display_pipe *disp;
unsigned int i;
int ret;
ldev->num_output = 0;
ports = of_get_child_by_name(ldev->dev->of_node, "ports");
for (i = 0; i < num_crtc; i++) {
struct drm_bridge *b;
struct drm_panel *p;
disp = &ldev->dispipe[i];
disp->available = false;
ret = drm_of_find_panel_or_bridge(ports, i, 0, &p, &b);
if (ret) {
if (ret == -ENODEV) {
drm_dbg(ddev, "No active panel or bridge for port%u\n", i);
disp->available = false;
continue;
}
if (ret == -EPROBE_DEFER)
drm_dbg(ddev, "Bridge for port%d is defer probed\n", i);
goto RET;
}
disp->available = true;
ldev->num_output++;
}
if (ldev->num_output == 0) {
drm_err(ddev, "No valid output, abort\n");
ret = -ENODEV;
goto RET;
}
for (i = 0; i < num_crtc; i++) {
disp = &ldev->dispipe[i];
if (disp->available) {
ret = lsdc_attach_bridges(ldev, ports, i);
if (ret)
goto RET;
} else {
drm_info(ddev, "output-%u is not available\n", i);
}
}
drm_info(ddev, "number of outputs: %u\n", ldev->num_output);
RET:
of_node_put(ports);
return ret;
}
/* No DT support, provide a minimal support */
int lsdc_create_output(struct lsdc_device *ldev,
unsigned int index,
unsigned int num_crtc)
{
const struct lsdc_chip_desc * const descp = ldev->desc;
struct lsdc_display_pipe * const dispipe = &ldev->dispipe[index];
struct drm_device *ddev = ldev->ddev;
int encoder_type = DRM_MODE_ENCODER_DPI;
int connector_type = DRM_MODE_CONNECTOR_DPI;
struct lsdc_output *output;
struct drm_encoder *encoder;
struct drm_connector *connector;
int ret;
output = devm_kzalloc(ddev->dev, sizeof(*output), GFP_KERNEL);
if (!output)
return -ENOMEM;
encoder = &output->encoder;
if (descp->chip == LSDC_CHIP_7A2000) {
encoder_type = DRM_MODE_ENCODER_TMDS;
connector_type = DRM_MODE_CONNECTOR_HDMIA;
}
ret = drm_encoder_init(ddev, encoder, &lsdc_encoder_funcs,
encoder_type, "encoder-%u", index);
if (ret) {
drm_err(ddev, "Failed to init encoder: %d\n", ret);
return ret;
}
if (descp->chip == LSDC_CHIP_7A2000)
drm_encoder_helper_add(encoder, &ls7a2000_hdmi_encoder_helper_funcs);
encoder->possible_crtcs = BIT(index);
if (descp->has_builtin_i2c) {
output->li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index);
if (IS_ERR(output->li2c)) {
drm_err(ddev, "Failed to create i2c adapter\n");
return PTR_ERR(output->li2c);
}
} else {
drm_warn(ddev, "output-%u don't has ddc\n", index);
output->li2c = NULL;
}
connector = &output->connector;
ret = drm_connector_init_with_ddc(ddev,
connector,
&lsdc_connector_funcs,
connector_type,
&output->li2c->adapter);
if (ret) {
drm_err(ddev, "Init connector%d failed\n", index);
return ret;
}
drm_connector_helper_add(connector, &lsdc_connector_helpers);
connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
drm_connector_attach_encoder(connector, encoder);
dispipe->available = true;
dispipe->output = output;
ldev->num_output++;
return 0;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_OUTPUT_H__
#define __LSDC_OUTPUT_H__
#include <drm/drm_device.h>
#include <drm/drm_connector.h>
int lsdc_create_output(struct lsdc_device *ldev, unsigned int i, unsigned int num_crtc);
int lsdc_attach_output(struct lsdc_device *ldev, uint32_t num_crtc);
#endif
// SPDX-License-Identifier: GPL-2.0+
/*
* KMS driver for Loongson display controller
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <drm/drm_modeset_helper.h>
#include "lsdc_drv.h"
#include "lsdc_i2c.h"
static int lsdc_use_vram_helper = -1;
MODULE_PARM_DESC(use_vram_helper, "Using vram helper based driver(0 = disabled)");
module_param_named(use_vram_helper, lsdc_use_vram_helper, int, 0644);
static int lsdc_gamma = -1;
MODULE_PARM_DESC(gamma, "enable gamma (-1 = disabled (default), >0 = enabled)");
module_param_named(gamma, lsdc_gamma, int, 0644);
static int lsdc_relax_alignment = -1;
MODULE_PARM_DESC(relax_alignment,
"relax crtc stride alignment (-1 = disabled (default), >0 = enabled)");
module_param_named(relax_alignment, lsdc_relax_alignment, int, 0644);
static struct platform_device *
lsdc_create_platform_device(const char *name,
struct device *parent,
const struct lsdc_chip_desc *descp,
struct resource *res)
{
struct device *dev;
struct platform_device *pdev;
int ret;
pdev = platform_device_alloc(name, PLATFORM_DEVID_NONE);
if (!pdev) {
dev_err(parent, "can not create platform device\n");
return ERR_PTR(-ENOMEM);
}
dev_info(parent, "platform device %s created\n", name);
dev = &pdev->dev;
dev->parent = parent;
if (descp) {
ret = platform_device_add_data(pdev, descp, sizeof(*descp));
if (ret) {
dev_err(parent, "add platform data failed: %d\n", ret);
goto ERROR_RET;
}
}
if (res) {
ret = platform_device_add_resources(pdev, res, 1);
if (ret) {
dev_err(parent, "add platform resources failed: %d\n", ret);
goto ERROR_RET;
}
}
ret = platform_device_add(pdev);
if (ret) {
dev_err(parent, "add platform device failed: %d\n", ret);
goto ERROR_RET;
}
return pdev;
ERROR_RET:
platform_device_put(pdev);
return ERR_PTR(ret);
}
static int lsdc_vram_init(struct lsdc_device *ldev)
{
const struct lsdc_chip_desc * const descp = ldev->desc;
struct pci_dev *gpu;
resource_size_t base, size;
if (descp->chip == LSDC_CHIP_7A2000) {
/* BAR 2 of LS7A2000's GPU contain VRAM */
gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL);
} else if (descp->chip == LSDC_CHIP_7A1000) {
/* BAR 2 of LS7A1000's GPU(GC1000) contain VRAM */
gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL);
} else {
dev_err(ldev->dev, "Unknown chip, the driver need update\n");
return -ENOENT;
}
if (IS_ERR_OR_NULL(gpu)) {
dev_err(ldev->dev, "Can not get VRAM\n");
return -ENOENT;
}
base = pci_resource_start(gpu, 2);
size = pci_resource_len(gpu, 2);
ldev->vram_base = base;
ldev->vram_size = size;
dev_info(ldev->dev, "vram start: 0x%llx, size: %uMB\n",
(u64)base, (u32)(size >> 20));
return 0;
}
static void lsdc_of_probe(struct lsdc_device *ldev, struct device_node *np)
{
struct device_node *ports;
if (!np) {
ldev->has_dt = false;
ldev->has_ports_node = false;
dev_info(ldev->dev, "don't has DT support\n");
return;
}
ports = of_get_child_by_name(np, "ports");
ldev->has_ports_node = ports ? true : false;
of_node_put(ports);
}
static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device *dev = &pdev->dev;
const struct lsdc_chip_desc *descp;
struct lsdc_device *ldev;
int ret;
descp = lsdc_detect_chip(pdev, ent);
if (!descp) {
dev_info(dev, "unknown dc ip core, abort\n");
return -ENOENT;
}
ldev = devm_kzalloc(dev, sizeof(*ldev), GFP_KERNEL);
if (IS_ERR(ldev))
return PTR_ERR(ldev);
ldev->desc = descp;
ldev->dev = dev;
if (lsdc_use_vram_helper > 0)
ldev->use_vram_helper = true;
else if ((lsdc_use_vram_helper < 0) && descp->has_vram)
ldev->use_vram_helper = true;
else
ldev->use_vram_helper = false;
if (!descp->broken_gamma)
ldev->enable_gamma = true;
else
ldev->enable_gamma = lsdc_gamma > 0 ? true : false;
ldev->relax_alignment = lsdc_relax_alignment > 0 ? true : false;
lsdc_of_probe(ldev, dev->of_node);
ret = pcim_enable_device(pdev);
if (ret)
return ret;
pci_set_master(pdev);
/* BAR 0 contains registers */
ldev->reg_base = devm_ioremap_resource(dev, &pdev->resource[0]);
if (IS_ERR(ldev->reg_base))
return PTR_ERR(ldev->reg_base);
/* Create GPIO emulated i2c driver as early as possible */
if (descp->has_builtin_i2c && ldev->has_ports_node) {
struct device_node *i2c_node;
for_each_compatible_node(i2c_node, NULL, "loongson,gpio-i2c") {
if (!of_device_is_available(i2c_node))
continue;
lsdc_of_create_i2c_adapter(dev, ldev->reg_base, i2c_node);
}
}
if (ldev->has_dt) {
/* Get the optional framebuffer memory resource */
ret = of_reserved_mem_device_init(dev);
if (ret && (ret != -ENODEV))
return ret;
}
if (descp->has_vram && ldev->use_vram_helper) {
ret = lsdc_vram_init(ldev);
if (ret) {
dev_err(dev, "VRAM is unavailable\n");
ldev->use_vram_helper = false;
}
}
ldev->irq = pdev->irq;
dev_set_drvdata(dev, ldev);
if (descp->has_vram && ldev->use_vram_helper) {
struct resource res;
memset(&res, 0, sizeof(res));
res.flags = IORESOURCE_MEM;
res.name = "LS7A_VRAM";
res.start = ldev->vram_base;
res.end = ldev->vram_size;
}
ldev->dc = lsdc_create_platform_device("lsdc", dev, descp, NULL);
if (IS_ERR(ldev->dc))
return PTR_ERR(ldev->dc);
return platform_driver_register(&lsdc_platform_driver);
}
static void lsdc_pci_remove(struct pci_dev *pdev)
{
struct lsdc_device *ldev = pci_get_drvdata(pdev);
platform_device_unregister(ldev->dc);
pci_set_drvdata(pdev, NULL);
pci_clear_master(pdev);
pci_release_regions(pdev);
}
static int lsdc_drm_suspend(struct device *dev)
{
struct lsdc_device *ldev = dev_get_drvdata(dev);
return drm_mode_config_helper_suspend(ldev->ddev);
}
static int lsdc_drm_resume(struct device *dev)
{
struct lsdc_device *ldev = dev_get_drvdata(dev);
return drm_mode_config_helper_resume(ldev->ddev);
}
static int lsdc_pm_freeze(struct device *dev)
{
return lsdc_drm_suspend(dev);
}
static int lsdc_pm_thaw(struct device *dev)
{
return lsdc_drm_resume(dev);
}
static int lsdc_pm_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
int error;
error = lsdc_pm_freeze(dev);
if (error)
return error;
pci_save_state(pdev);
/* Shut down the device */
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
return 0;
}
static int lsdc_pm_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
if (pcim_enable_device(pdev))
return -EIO;
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
return lsdc_pm_thaw(dev);
}
static const struct dev_pm_ops lsdc_pm_ops = {
.suspend = lsdc_pm_suspend,
.resume = lsdc_pm_resume,
.freeze = lsdc_pm_freeze,
.thaw = lsdc_pm_thaw,
.poweroff = lsdc_pm_freeze,
.restore = lsdc_pm_resume,
};
static const struct pci_device_id lsdc_pciid_list[] = {
{PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)LSDC_CHIP_7A1000},
{PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)LSDC_CHIP_7A2000},
{0, 0, 0, 0, 0, 0, 0}
};
static struct pci_driver lsdc_pci_driver = {
.name = DRIVER_NAME,
.id_table = lsdc_pciid_list,
.probe = lsdc_pci_probe,
.remove = lsdc_pci_remove,
.driver.pm = &lsdc_pm_ops,
};
static int __init lsdc_drm_init(void)
{
struct pci_dev *pdev = NULL;
while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
/*
* Multiple video card workaround
*
* This integrated video card will always be selected as
* default boot device by vgaarb subsystem.
*/
if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) {
pr_info("Discrete graphic card detected, abort\n");
return 0;
}
}
return pci_register_driver(&lsdc_pci_driver);
}
module_init(lsdc_drm_init);
static void __exit lsdc_drm_exit(void)
{
pci_unregister_driver(&lsdc_pci_driver);
}
module_exit(lsdc_drm_exit);
MODULE_DEVICE_TABLE(pci, lsdc_pciid_list);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL v2");
// 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;
}
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#include "lsdc_drv.h"
#include "lsdc_regs.h"
#include "lsdc_pll.h"
/*
* The structure of the pixel PLL register is evolved with times.
* All loongson's cpu is little endian.
*/
/* u64 */
struct ls7a1000_pixpll_bitmap {
/* Byte 0 ~ Byte 3 */
unsigned div_out : 7; /* 0 : 6 output clock divider */
unsigned reserved_1 : 14; /* 7 : 20 */
unsigned loopc : 9; /* 21 : 29 */
unsigned reserved_2 : 2; /* 30 : 31 */
/* Byte 4 ~ Byte 7 */
unsigned div_ref : 7; /* 0 : 6 input clock divider */
unsigned locked : 1; /* 7 PLL locked flag */
unsigned sel_out : 1; /* 8 output clk selector */
unsigned reserved_3 : 2; /* 9 : 10 reserved */
unsigned set_param : 1; /* 11 set pll param */
unsigned bypass : 1; /* 12 */
unsigned powerdown : 1; /* 13 */
unsigned reserved_4 : 18; /* 14 : 31 */
};
/* u128 */
struct ls2k1000_pixpll_bitmap {
/* Byte 0 ~ Byte 3 */
unsigned sel_out : 1; /* 0 select this PLL */
unsigned reserved_1 : 1; /* 1 */
unsigned sw_adj_en : 1; /* 2 allow software adjust */
unsigned bypass : 1; /* 3 bypass L1 PLL */
unsigned reserved_2 : 3; /* 4:6 */
unsigned lock_en : 1; /* 7 enable lock L1 PLL */
unsigned reserved_3 : 2; /* 8:9 */
unsigned lock_check : 2; /* 10:11 precision check */
unsigned reserved_4 : 4; /* 12:15 */
unsigned locked : 1; /* 16 PLL locked flag bit */
unsigned reserved_5 : 2; /* 17:18 */
unsigned powerdown : 1; /* 19 powerdown the pll if set */
unsigned reserved_6 : 6; /* 20:25 */
unsigned div_ref : 6; /* 26:31 L1 Prescaler */
/* Byte 4 ~ Byte 7 */
unsigned loopc : 10; /* 32:41 Clock Multiplier */
unsigned l1_div : 6; /* 42:47 not used */
unsigned reserved_7 : 16; /* 48:63 */
/* Byte 8 ~ Byte 15 */
unsigned div_out : 6; /* 0 : 5 output clock divider */
unsigned reserved_8 : 26; /* 6 : 31 */
unsigned reserved_9 : 32; /* 70: 127 */
};
/* u32 */
struct ls2k0500_pixpll_bitmap {
/* Byte 0 ~ Byte 1 */
unsigned sel_out : 1;
unsigned reserved_1 : 2;
unsigned sw_adj_en : 1; /* allow software adjust */
unsigned bypass : 1; /* bypass L1 PLL */
unsigned powerdown : 1; /* write 1 to powerdown the PLL */
unsigned reserved_2 : 1;
unsigned locked : 1; /* 7 Is L1 PLL locked, read only */
unsigned div_ref : 6; /* 8:13 ref clock divider */
unsigned reserved_3 : 2; /* 14:15 */
/* Byte 2 ~ Byte 3 */
unsigned loopc : 8; /* 16:23 Clock Multiplier */
unsigned div_out : 6; /* 24:29 output clock divider */
unsigned reserved_4 : 2; /* 30:31 */
};
union lsdc_pixpll_bitmap {
struct ls7a1000_pixpll_bitmap ls7a2000;
struct ls7a1000_pixpll_bitmap ls7a1000;
struct ls2k1000_pixpll_bitmap ls2k1000;
struct ls2k0500_pixpll_bitmap ls2k0500;
u32 dword[4];
};
struct pixclk_to_pll_parm {
/* kHz */
unsigned int clock;
/* unrelated information */
unsigned short width;
unsigned short height;
unsigned short vrefresh;
/* Stores parameters for programming the Hardware PLLs */
unsigned short div_out;
unsigned short loopc;
unsigned short div_ref;
};
/*
* Pixel clock to PLL parameters translation table.
* Small static cached value to speed up PLL parameters calculation.
*/
static const struct pixclk_to_pll_parm pll_param_table[] = {
{148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */
/* 1920x1080@50Hz */
{174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */
{181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */
{146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */
{135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */
{108000, 1600, 900, 60, 15, 81, 5}, /* 1600x900@60Hz */
/* 1280x1024@60Hz */
/* 1280x960@60Hz */
/* 1152x864@75Hz */
{106500, 1440, 900, 60, 19, 81, 4}, /* 1440x900@60Hz */
{88750, 1440, 900, 60, 16, 71, 5}, /* 1440x900@60Hz */
{83500, 1280, 800, 60, 17, 71, 5}, /* 1280x800@60Hz */
{71000, 1280, 800, 60, 20, 71, 5}, /* 1280x800@60Hz */
{74250, 1280, 720, 60, 22, 49, 3}, /* 1280x720@60Hz */
/* 1280x720@50Hz */
{78750, 1024, 768, 75, 16, 63, 5}, /* 1024x768@75Hz */
{75000, 1024, 768, 70, 29, 87, 4}, /* 1024x768@70Hz */
{65000, 1024, 768, 60, 20, 39, 3}, /* 1024x768@60Hz */
{51200, 1024, 600, 60, 25, 64, 5}, /* 1024x600@60Hz */
{57284, 832, 624, 75, 24, 55, 4}, /* 832x624@75Hz */
{49500, 800, 600, 75, 40, 99, 5}, /* 800x600@75Hz */
{50000, 800, 600, 72, 44, 88, 4}, /* 800x600@72Hz */
{40000, 800, 600, 60, 30, 36, 3}, /* 800x600@60Hz */
{36000, 800, 600, 56, 50, 72, 4}, /* 800x600@56Hz */
{31500, 640, 480, 75, 40, 63, 5}, /* 640x480@75Hz */
/* 640x480@73Hz */
{30240, 640, 480, 67, 62, 75, 4}, /* 640x480@67Hz */
{27000, 720, 576, 50, 50, 54, 4}, /* 720x576@60Hz */
{25175, 640, 480, 60, 85, 107, 5}, /* 640x480@60Hz */
{25200, 640, 480, 60, 50, 63, 5}, /* 640x480@60Hz */
/* 720x480@60Hz */
};
/**
* lsdc_pixpll_setup - ioremap the device dependent PLL registers
*
* @this: point to the object which this function is called from
*/
static int lsdc_pixpll_setup(struct lsdc_pll * const this)
{
this->mmio = ioremap(this->reg_base, this->reg_size);
return 0;
}
/*
* Find a set of pll parameters (to generate pixel clock) from a static
* local table, which avoid to compute the pll parameter eachtime a
* modeset is triggered.
*
* @this: point to the object which this function is called from
* @clock: the desired output pixel clock, the unit is kHz
* @pout: point to where the parameters to store if found
*
* Return true if hit, otherwise return false.
*/
static bool lsdc_pixpll_find(struct lsdc_pll * const this,
unsigned int clock,
struct lsdc_pll_core_values * const pout)
{
unsigned int num = ARRAY_SIZE(pll_param_table);
unsigned int i;
for (i = 0; i < num; i++) {
if (clock != pll_param_table[i].clock)
continue;
pout->div_ref = pll_param_table[i].div_ref;
pout->loopc = pll_param_table[i].loopc;
pout->div_out = pll_param_table[i].div_out;
return true;
}
drm_dbg(this->ddev, "pixel clock %u: miss\n", clock);
return false;
}
/*
* Find a set of pll parameters which have minimal difference with the desired
* pixel clock frequency. It does that by computing all of the possible
* combination. Compute the diff and find the combination with minimal diff.
*
* clock_out = refclk / div_ref * loopc / div_out
*
* refclk is fixed as 100MHz in ls7a1000, ls2k1000 and ls2k0500
*
* @this: point to the object from which this function is called
* @clk: the desired output pixel clock, the unit is kHz
* @pout: point to where the parameters to store if success
*
* Return true if a parameter is found, otherwise return false
*/
static bool lsdc_pixpll_compute(struct lsdc_pll * const this,
unsigned int clk,
struct lsdc_pll_core_values *pout)
{
unsigned int refclk = this->ref_clock;
const unsigned int tolerance = 1000;
unsigned int min = tolerance;
unsigned int div_out, loopc, div_ref;
if (lsdc_pixpll_find(this, clk, pout))
return true;
for (div_out = 6; div_out < 64; div_out++) {
for (div_ref = 3; div_ref < 6; div_ref++) {
for (loopc = 6; loopc < 161; loopc++) {
int diff;
if (loopc < 12 * div_ref)
continue;
if (loopc > 32 * div_ref)
continue;
diff = clk * div_out - refclk * loopc / div_ref;
if (diff < 0)
diff = -diff;
if (diff < min) {
min = diff;
pout->div_ref = div_ref;
pout->div_out = div_out;
pout->loopc = loopc;
if (diff == 0)
return true;
}
}
}
}
return min < tolerance;
}
/*
* Update the pll parameters to hardware, target to the pixpll in ls7a1000
*
* @this: point to the object from which this function is called
* @param: point to the core parameters passed in
*
* return 0 if successful.
*/
static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this,
struct lsdc_pll_core_values const *param)
{
void __iomem *reg = this->mmio;
unsigned int counter = 0;
bool locked;
u32 val;
/* Bypass the software configured PLL, using refclk directly */
val = readl(reg + 0x4);
val &= ~(1 << 8);
writel(val, reg + 0x4);
/* Powerdown the PLL */
val = readl(reg + 0x4);
val |= (1 << 13);
writel(val, reg + 0x4);
/* Clear the pll parameters */
val = readl(reg + 0x4);
val &= ~(1 << 11);
writel(val, reg + 0x4);
/* clear old value & config new value */
val = readl(reg + 0x04);
val &= ~0x7F;
val |= param->div_ref; /* div_ref */
writel(val, reg + 0x4);
val = readl(reg);
val &= ~0x7f;
val |= param->div_out; /* div_out */
val &= ~(0x1ff << 21);
val |= param->loopc << 21; /* loopc */
writel(val, reg);
/* Set the pll the parameters */
val = readl(reg + 0x4);
val |= (1 << 11);
writel(val, reg + 0x4);
/* Powerup the PLL */
val = readl(reg + 0x4);
val &= ~(1 << 13);
writel(val, reg + 0x4);
/* Wait the PLL lock */
do {
val = readl(reg + 0x4);
locked = val & 0x80;
counter++;
} while (!locked && (counter < 10000));
drm_dbg(this->ddev, "%u loop waited\n", counter);
/* Switch to the software configured pll */
val = readl(reg + 0x4);
val |= (1UL << 8);
writel(val, reg + 0x4);
return 0;
}
/*
* Update the pll parameters to hardware, target to the pixpll in ls2k1000
*
* @this: point to the object from which this function is called
* @param: pointer to where the parameter is passed in
*
* return 0 if successful.
*/
static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this,
struct lsdc_pll_core_values const *param)
{
void __iomem *reg = this->mmio;
unsigned int counter = 0;
bool locked = false;
u32 val;
val = readl(reg);
/* Bypass the software configured PLL, using refclk directly */
val &= ~(1 << 0);
writel(val, reg);
/* Powerdown the PLL */
val |= (1 << 19);
writel(val, reg);
/* Allow the software configuration */
val &= ~(1 << 2);
writel(val, reg);
/* allow L1 PLL lock */
val = (1L << 7) | (3L << 10);
writel(val, reg);
/* clear div_ref bit field */
val &= ~(0x3f << 26);
/* set div_ref bit field */
val = val | (param->div_ref << 26);
writel(val, reg);
val = readl(reg + 4);
/* clear loopc bit field */
val &= ~0x0fff;
/* set loopc bit field */
val |= param->loopc;
writel(val, reg + 4);
/* set div_out */
writel(param->div_out, reg + 8);
val = readl(reg);
/* use the software configure param */
val |= (1 << 2);
/* powerup the PLL */
val &= ~(1 << 19);
writel(val, reg);
/* wait pll setup and locked */
do {
val = readl(reg);
locked = val & 0x10000;
counter++;
} while (!locked && (counter < 10000));
drm_dbg(this->ddev, "%u loop waited\n", counter);
/* Switch to the above software configured PLL instead of refclk */
val |= 1;
writel(val, reg);
return 0;
}
/*
* Update the pll parameters to hardware, target to the pixpll in ls2k0500
*
* @this: point to the object which calling this function
* @param: pointer to where the parameters passed in
*
* return 0 if successful.
*/
static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this,
struct lsdc_pll_core_values const *param)
{
void __iomem *reg = this->mmio;
unsigned int counter = 0;
bool locked = false;
u32 val;
/* Bypass the software configured PLL, using refclk directly */
val = readl(reg);
val &= ~(1 << 0);
writel(val, reg);
/* Powerdown the PLL */
val = readl(reg);
val |= (1 << 5);
writel(val, reg);
/* Allow the software configuration */
val |= (1 << 3);
writel(val, reg);
/* Update the pll params */
val = (param->div_out << 24) |
(param->loopc << 16) |
(param->div_ref << 8);
writel(val, reg);
/* Powerup the PLL */
val = readl(reg);
val &= ~(1 << 5);
writel(val, reg);
/* wait pll setup and locked */
do {
val = readl(reg);
locked = val & 0x80;
counter++;
} while (!locked && (counter < 10000));
drm_dbg(this->ddev, "%u loop waited\n", counter);
/* Switch to the above software configured PLL instead of refclk */
writel((val | 1), reg);
return 0;
}
static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this,
struct lsdc_pll_core_values *pout)
{
struct drm_device *ddev = this->ddev;
struct lsdc_device *ldev = to_lsdc(ddev);
const struct lsdc_chip_desc * const desc = ldev->desc;
unsigned int out;
union lsdc_pixpll_bitmap parms;
if (desc->chip == LSDC_CHIP_7A2000) {
struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a2000;
parms.dword[0] = readl(this->mmio);
parms.dword[1] = readl(this->mmio + 4);
out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out;
if (pout) {
pout->div_ref = obj->div_ref;
pout->loopc = obj->loopc;
pout->div_out = obj->div_out;
}
} else if (desc->chip == LSDC_CHIP_7A1000) {
struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000;
parms.dword[0] = readl(this->mmio);
parms.dword[1] = readl(this->mmio + 4);
out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out;
if (pout) {
pout->div_ref = obj->div_ref;
pout->loopc = obj->loopc;
pout->div_out = obj->div_out;
}
} else if (desc->chip == LSDC_CHIP_2K1000) {
struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000;
parms.dword[0] = readl(this->mmio);
parms.dword[1] = readl(this->mmio + 4);
parms.dword[2] = readl(this->mmio + 8);
parms.dword[3] = readl(this->mmio + 12);
out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out;
if (pout) {
pout->div_ref = obj->div_ref;
pout->loopc = obj->loopc;
pout->div_out = obj->div_out;
}
} else if (desc->chip == LSDC_CHIP_2K0500) {
struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500;
parms.dword[0] = readl(this->mmio);
out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out;
if (pout) {
pout->div_ref = obj->div_ref;
pout->loopc = obj->loopc;
pout->div_out = obj->div_out;
}
} else {
drm_err(ddev, "unknown chip, the driver need update\n");
return 0;
}
return out;
}
static const struct lsdc_pixpll_funcs ls7a2000_pixpll_funcs = {
.setup = lsdc_pixpll_setup,
.compute = lsdc_pixpll_compute,
.update = ls7a1000_pixpll_param_update,
.get_clock_rate = lsdc_get_clock_rate,
};
static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = {
.setup = lsdc_pixpll_setup,
.compute = lsdc_pixpll_compute,
.update = ls7a1000_pixpll_param_update,
.get_clock_rate = lsdc_get_clock_rate,
};
static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = {
.setup = lsdc_pixpll_setup,
.compute = lsdc_pixpll_compute,
.update = ls2k1000_pixpll_param_update,
.get_clock_rate = lsdc_get_clock_rate,
};
static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = {
.setup = lsdc_pixpll_setup,
.compute = lsdc_pixpll_compute,
.update = ls2k0500_pixpll_param_update,
.get_clock_rate = lsdc_get_clock_rate,
};
int lsdc_pixpll_init(struct lsdc_pll * const this,
struct drm_device *ddev,
unsigned int index)
{
struct lsdc_device *ldev = to_lsdc(ddev);
const struct lsdc_chip_desc * const descp = ldev->desc;
this->ddev = ddev;
this->index = index;
this->ref_clock = LSDC_PLL_REF_CLK;
if (descp->chip == LSDC_CHIP_7A2000) {
if (index == 0)
this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL0_REG;
else if (index == 1)
this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL1_REG;
this->reg_size = 8;
this->funcs = &ls7a2000_pixpll_funcs;
} else if (descp->chip == LSDC_CHIP_7A1000) {
if (index == 0)
this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL0_REG;
else if (index == 1)
this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL1_REG;
this->reg_size = 8;
this->funcs = &ls7a1000_pixpll_funcs;
} else if (descp->chip == LSDC_CHIP_2K1000) {
if (index == 0)
this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL0_REG;
else if (index == 1)
this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL1_REG;
this->reg_size = 16;
this->funcs = &ls2k1000_pixpll_funcs;
} else if (descp->chip == LSDC_CHIP_2K0500) {
if (index == 0)
this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL0_REG;
else if (index == 1)
this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL1_REG;
this->reg_size = 4;
this->funcs = &ls2k0500_pixpll_funcs;
} else {
drm_err(this->ddev, "unknown chip, the driver need update\n");
return -ENOENT;
}
return this->funcs->setup(this);
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_PLL_H__
#define __LSDC_PLL_H__
#include <drm/drm_device.h>
/*
* PIXEL PLL hardware structure
*
* refclk: reference frequency, 100 MHz from external oscillator
* outclk: output frequency desired.
*
*
* L1 Fref Fvco L2
* refclk +-----------+ +------------------+ +---------+ outclk
* ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider | -------->
* | +-----------+ +------------------+ +---------+ ^
* | ^ ^ ^ |
* | | | | |
* | | | | |
* | div_ref loopc div_out |
* | |
* +---- sel_out (bypass above software configurable clock if==1) ----+
*
* sel_out: PLL clock output selector.
*
* If sel_out == 1, it will take refclk as output directly,
* the L1 Prescaler and the out divider is bypassed.
*
* If sel_out == 0, then:
* outclk = refclk / div_ref * loopc / div_out;
*
* PLL hardware working requirements:
*
* 1) 20 MHz <= refclk / div_ref <= 40Mhz
* 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz
*
*/
struct lsdc_pll_core_values {
unsigned int div_ref;
unsigned int loopc;
unsigned int div_out;
};
struct lsdc_pll;
struct lsdc_pixpll_funcs {
int (*setup)(struct lsdc_pll * const this);
bool (*compute)(struct lsdc_pll * const this, unsigned int clock,
struct lsdc_pll_core_values *params_out);
int (*update)(struct lsdc_pll * const this,
struct lsdc_pll_core_values const *params_in);
unsigned int (*get_clock_rate)(struct lsdc_pll * const this,
struct lsdc_pll_core_values *pout);
};
struct lsdc_pll {
const struct lsdc_pixpll_funcs *funcs;
struct drm_device *ddev;
void __iomem *mmio;
/* PLL register offset */
u32 reg_base;
/* PLL register size in bytes */
u32 reg_size;
/* 100000kHz, fixed on all board found */
unsigned int ref_clock;
unsigned int index;
};
int lsdc_pixpll_init(struct lsdc_pll * const this,
struct drm_device *ddev,
unsigned int index);
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2022 Loongson Corporation
*/
/*
* Authors:
* Sui Jingfeng <suijingfeng@loongson.cn>
*/
#ifndef __LSDC_REGS_H__
#define __LSDC_REGS_H__
#include <linux/bitops.h>
#include <linux/types.h>
/*
* PIXEL PLL
*/
#define LSDC_PLL_REF_CLK 100000 /* kHz */
/*
* Those PLL registers are not located at DC reg bar space,
* there are relative to LSXXXXX_CFG_REG_BASE.
* XXXXX = 7A1000, 2K1000, 2K0500
*/
/* LS2K1000 */
#define LS2K1000_PIX_PLL0_REG 0x04B0
#define LS2K1000_PIX_PLL1_REG 0x04C0
#define LS2K1000_CFG_REG_BASE 0x1fe10000
/* LS7A1000 */
#define LS7A1000_PIX_PLL0_REG 0x04B0
#define LS7A1000_PIX_PLL1_REG 0x04C0
#define LS7A1000_CFG_REG_BASE 0x10010000
/* LS2K0500 */
#define LS2K0500_PIX_PLL0_REG 0x0418
#define LS2K0500_PIX_PLL1_REG 0x0420
#define LS2K0500_CFG_REG_BASE 0x1fe10000
/*
* CRTC CFG REG
*/
#define CFG_PIX_FMT_MASK GENMASK(2, 0)
enum lsdc_pixel_format {
LSDC_PF_NONE = 0,
LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */
LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */
LSDC_PF_RGB565 = 3, /* RGB [16 bits] */
LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */
};
/* Each CRTC has two FB address registers, CFG_FB_IDX_BIT specify
* which fb address register is currently in using by the CRTC.
* Setting CFG_PAGE_FLIP_BIT bit will triger the switch. The switch
* finished at the vblank and if you want switch back you can set
* CFG_PAGE_FLIP_BIT again.
*/
#define CFG_PAGE_FLIP_BIT BIT(7)
#define CFG_OUTPUT_EN_BIT BIT(8)
/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic */
#define CFG_PANEL_SWITCH BIT(9)
/* Indicate witch fb addr reg is in using, currently */
#define CFG_FB_IDX_BIT BIT(11)
#define CFG_GAMMAR_EN_BIT BIT(12)
/* CRTC get soft reset if voltage level change from 1 -> 0 */
#define CFG_RESET_BIT BIT(20)
#define EN_HSYNC_BIT BIT(30)
#define INV_HSYNC_BIT BIT(31)
#define EN_VSYNC_BIT BIT(30)
#define INV_VSYNC_BIT BIT(31)
/******** CRTC0 & DVO0 ********/
#define LSDC_CRTC0_CFG_REG 0x1240
#define LSDC_CRTC0_FB_ADDR0_REG 0x1260
#define LSDC_CRTC0_FB_ADDR1_REG 0x1580
#define LSDC_CRTC0_FB_HI_ADDR_REG 0x15A0
#define LSDC_CRTC0_STRIDE_REG 0x1280
#define LSDC_CRTC0_FB_ORIGIN_REG 0x1300
#define LSDC_CRTC0_HDISPLAY_REG 0x1400
#define LSDC_CRTC0_HSYNC_REG 0x1420
#define LSDC_CRTC0_VDISPLAY_REG 0x1480
#define LSDC_CRTC0_VSYNC_REG 0x14A0
#define LSDC_CRTC0_GAMMA_INDEX_REG 0x14E0
#define LSDC_CRTC0_GAMMA_DATA_REG 0x1500
/******** CTRC1 & DVO1 ********/
#define LSDC_CRTC1_CFG_REG 0x1250
#define LSDC_CRTC1_FB_ADDR0_REG 0x1270
#define LSDC_CRTC1_FB_ADDR1_REG 0x1590
#define LSDC_CRTC1_FB_HI_ADDR_REG 0x15C0
#define LSDC_CRTC1_STRIDE_REG 0x1290
#define LSDC_CRTC1_FB_ORIGIN_REG 0x1310
#define LSDC_CRTC1_HDISPLAY_REG 0x1410
#define LSDC_CRTC1_HSYNC_REG 0x1430
#define LSDC_CRTC1_VDISPLAY_REG 0x1490
#define LSDC_CRTC1_VSYNC_REG 0x14B0
#define LSDC_CRTC1_GAMMA_INDEX_REG 0x14F0
#define LSDC_CRTC1_GAMMA_DATA_REG 0x1510
#define LSDC_REGS_OFFSET 0x0010
/*
* Hardware cursor
* There is only one hardware cursor shared by two CRTC in ls7a1000,
* ls2k1000 and ls2k0500.
*/
#define LSDC_CURSOR0_CFG_REG 0x1520
#define LSDC_CURSOR0_ADDR_REG 0x1530
#define LSDC_CURSOR0_POSITION_REG 0x1540
#define LSDC_CURSOR0_BG_COLOR_REG 0x1550 /* background color */
#define LSDC_CURSOR0_FG_COLOR_REG 0x1560 /* foreground color */
#define LSDC_CURS_MIN_SIZE 1
#define LSDC_CURS_MAX_SIZE 64
#define CURSOR_FORMAT_MASK GENMASK(1, 0)
#define CURSOR_FORMAT_DISABLE 0
#define CURSOR_FORMAT_MONOCHROME 1
#define CURSOR_FORMAT_ARGB8888 2
#define CURSOR_SIZE_64X64 BIT(2)
#define CURSOR_LOCATION_BIT BIT(4)
/* LS7A2000 have two hardware cursor */
#define LSDC_CURSOR1_CFG_REG 0x1670
#define LSDC_CURSOR1_ADDR_REG 0x1680
#define LSDC_CURSOR1_POSITION_REG 0x1690
#define LSDC_CURSOR1_BG_COLOR_REG 0x16A0 /* background color */
#define LSDC_CURSOR1_FG_COLOR_REG 0x16B0 /* foreground color */
/*
* DC Interrupt Control Register, 32bit, Address Offset: 1570
*
* Bits 0:10 inidicate the interrupt type, read only
* Bits 16:26 control if the specific interrupt corresponding to bit 0~10
* is enabled or not. Write 1 to enable, write 0 to disable
*
* RF: Read Finished
* IDBU : Internal Data Buffer Underflow
* IDBFU : Internal Data Buffer Fatal Underflow
*
*
* +-------+-------------------------------+-------+--------+--------+-------+
* | 31:27 | 26:16 | 15:11 | 10 | 9 | 8 |
* +-------+-------------------------------+-------+--------+--------+-------+
* | N/A | Interrupt Enable Control Bits | N/A | IDBFU0 | IDBFU1 | IDBU0 |
* +-------+-------------------------------+-------+--------+--------+-------+
*
* Bit 4 is cursor buffer read finished, no use.
*
* +-------+-----+-----+-----+--------+--------+--------+--------+
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +-------+-----+-----+-----+--------+--------+--------+--------+
* | IDBU1 | RF0 | RF1 | | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 |
* +-------+-----+-----+-----+--------+--------+--------+--------+
*
*/
#define LSDC_INT_REG 0x1570
#define INT_CRTC0_VS BIT(2)
#define INT_CRTC0_HS BIT(3)
#define INT_CRTC0_RF BIT(6)
#define INT_CRTC0_IDBU BIT(8)
#define INT_CRTC0_IDBFU BIT(10)
#define INT_CURSOR_RF BIT(4)
#define INT_CRTC1_VS BIT(0)
#define INT_CRTC1_HS BIT(1)
#define INT_CRTC1_RF BIT(5)
#define INT_CRTC1_IDBU BIT(7)
#define INT_CRTC1_IDBFU BIT(9)
#define INT_CRTC0_VS_EN BIT(18)
#define INT_CRTC0_HS_EN BIT(19)
#define INT_CRTC0_RF_EN BIT(22)
#define INT_CRTC0_IDBU_EN BIT(24)
#define INT_CRTC0_IDBFU_EN BIT(26)
#define INT_CURSOR_RF_EN BIT(20)
#define INT_CRTC1_VS_EN BIT(16)
#define INT_CRTC1_HS_EN BIT(17)
#define INT_CRTC1_RF_EN BIT(21)
#define INT_CRTC1_IDBU_EN BIT(23)
#define INT_CRTC1_IDBFU_EN BIT(25)
#define INT_STATUS_MASK GENMASK(10, 0)
/*
* LS7A1000 have 4 gpios which is under control of the LS7A_DC_GPIO_DAT_REG
* and LS7A_DC_GPIO_DIR_REG register, it has no relationship whth the general
* GPIO hardware. Those registers are in the DC register space on LS7A1000.
*
* Those GPIOs are used to emulated I2C, for reading edid and monitor detection
*
* LS2k1000 and LS2K0500 don't have those registers, they use hardware i2c or
* generial GPIO emulated i2c from other module.
*
* GPIO data register
* Address offset: 0x1650
* +---------------+-----------+-----------+
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
* +---------------+-----------+-----------+
* | | DVO1 | DVO0 |
* + N/A +-----------+-----------+
* | | SCL | SDA | SCL | SDA |
* +---------------+-----------+-----------+
*/
#define LS7A_DC_GPIO_DAT_REG 0x1650
/*
* GPIO Input/Output direction control register
* Address offset: 0x1660
* write 1 for Input, 0 for Output.
*/
#define LS7A_DC_GPIO_DIR_REG 0x1660
/*
* LS7A2000 Built-in HDMI Encoder
*/
#define HDMI_EN BIT(0)
#define HDMI_PACKET_EN BIT(1)
#define HDMI0_ZONE_REG 0x1700
#define HDMI1_ZONE_REG 0x1710
#define HDMI0_CTRL_REG 0x1720
#define HDMI1_CTRL_REG 0x1730
#define HDMI_PLL_EN BIT(0)
#define HDMI_PLL_LOCKED BIT(16)
#define HDMI0_PHY_CTRL_REG 0x1800
#define HDMI0_PLL_REG 0x1820
#define HDMI1_PHY_CTRL_REG 0x1810
#define HDMI1_PLL_REG 0x1830
#define LS7A2000_DMA_STEP_MASK GENMASK(17, 16)
#define DMA_STEP_256_BYTE (0 << 16)
#define DMA_STEP_128_BYTE (1 << 16)
#define DMA_STEP_64_BYTE (2 << 16)
#define DMA_STEP_32_BYTE (3 << 16)
#endif
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册