From 80764bc784413eb604c7d472db55b1ca72d4bbc5 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Tue, 22 May 2018 16:54:04 +0800 Subject: [PATCH] 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: Jingfeng Sui Signed-off-by: Huacai Chen --- arch/loongarch/configs/loongson3_defconfig | 1 + drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/loongson/Kconfig | 26 + drivers/gpu/drm/loongson/Makefile | 16 + drivers/gpu/drm/loongson/lsdc_crtc.c | 420 +++++++++++++++ drivers/gpu/drm/loongson/lsdc_debugfs.c | 176 ++++++ drivers/gpu/drm/loongson/lsdc_debugfs.h | 17 + drivers/gpu/drm/loongson/lsdc_drv.c | 427 +++++++++++++++ drivers/gpu/drm/loongson/lsdc_drv.h | 184 +++++++ drivers/gpu/drm/loongson/lsdc_i2c.c | 288 ++++++++++ drivers/gpu/drm/loongson/lsdc_i2c.h | 38 ++ drivers/gpu/drm/loongson/lsdc_irq.c | 57 ++ drivers/gpu/drm/loongson/lsdc_irq.h | 17 + drivers/gpu/drm/loongson/lsdc_output.c | 412 ++++++++++++++ drivers/gpu/drm/loongson/lsdc_output.h | 21 + drivers/gpu/drm/loongson/lsdc_pci_drv.c | 352 ++++++++++++ drivers/gpu/drm/loongson/lsdc_plane.c | 476 ++++++++++++++++ drivers/gpu/drm/loongson/lsdc_pll.c | 599 +++++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_pll.h | 87 +++ drivers/gpu/drm/loongson/lsdc_regs.h | 252 +++++++++ 21 files changed, 3869 insertions(+) create mode 100644 drivers/gpu/drm/loongson/Kconfig create mode 100644 drivers/gpu/drm/loongson/Makefile create mode 100644 drivers/gpu/drm/loongson/lsdc_crtc.c create mode 100644 drivers/gpu/drm/loongson/lsdc_debugfs.c create mode 100644 drivers/gpu/drm/loongson/lsdc_debugfs.h create mode 100644 drivers/gpu/drm/loongson/lsdc_drv.c create mode 100644 drivers/gpu/drm/loongson/lsdc_drv.h create mode 100644 drivers/gpu/drm/loongson/lsdc_i2c.c create mode 100644 drivers/gpu/drm/loongson/lsdc_i2c.h create mode 100644 drivers/gpu/drm/loongson/lsdc_irq.c create mode 100644 drivers/gpu/drm/loongson/lsdc_irq.h create mode 100644 drivers/gpu/drm/loongson/lsdc_output.c create mode 100644 drivers/gpu/drm/loongson/lsdc_output.h create mode 100644 drivers/gpu/drm/loongson/lsdc_pci_drv.c create mode 100644 drivers/gpu/drm/loongson/lsdc_plane.c create mode 100644 drivers/gpu/drm/loongson/lsdc_pll.c create mode 100644 drivers/gpu/drm/loongson/lsdc_pll.h create mode 100644 drivers/gpu/drm/loongson/lsdc_regs.h diff --git a/arch/loongarch/configs/loongson3_defconfig b/arch/loongarch/configs/loongson3_defconfig index 8e15593b052a..e4eb90e9d86d 100644 --- a/arch/loongarch/configs/loongson3_defconfig +++ b/arch/loongarch/configs/loongson3_defconfig @@ -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 diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ca868271f4c4..af407d01e1c4 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -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" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 81569009f884..e9dd6847c9fa 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -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/ diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig new file mode 100644 index 000000000000..5d747271ffaf --- /dev/null +++ b/drivers/gpu/drm/loongson/Kconfig @@ -0,0 +1,26 @@ +# 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". diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile new file mode 100644 index 000000000000..cf6391b8eb17 --- /dev/null +++ b/drivers/gpu/drm/loongson/Makefile @@ -0,0 +1,16 @@ +# 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 diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c new file mode 100644 index 000000000000..e59b75d79098 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ +#include +#include +#include + +#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; +} diff --git a/drivers/gpu/drm/loongson/lsdc_debugfs.c b/drivers/gpu/drm/loongson/lsdc_debugfs.c new file mode 100644 index 000000000000..1aad413ee97c --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include +#include +#include +#include +#include + +#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, ¶ms); + + 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 diff --git a/drivers/gpu/drm/loongson/lsdc_debugfs.h b/drivers/gpu/drm/loongson/lsdc_debugfs.h new file mode 100644 index 000000000000..d5e630534f83 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#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 diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c new file mode 100644 index 000000000000..306b0de0d2e8 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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", + }, +}; diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h new file mode 100644 index 000000000000..e07b049dcd21 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#ifndef __LSDC_DRV_H__ +#define __LSDC_DRV_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lsdc_pll.h" + +#define DRIVER_AUTHOR "Sui Jingfeng " +#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 diff --git a/drivers/gpu/drm/loongson/lsdc_i2c.c b/drivers/gpu/drm/loongson/lsdc_i2c.c new file mode 100644 index 000000000000..925bb8b866ad --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_i2c.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include + +#include +#include +#include + +#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; +} diff --git a/drivers/gpu/drm/loongson/lsdc_i2c.h b/drivers/gpu/drm/loongson/lsdc_i2c.h new file mode 100644 index 000000000000..62cbf2aaab2e --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_i2c.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#ifndef __LSDC_I2C__ +#define __LSDC_I2C__ + +#include +#include +#include + +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 diff --git a/drivers/gpu/drm/loongson/lsdc_irq.c b/drivers/gpu/drm/loongson/lsdc_irq.c new file mode 100644 index 000000000000..35616f976fad --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_irq.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include + +#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; +} diff --git a/drivers/gpu/drm/loongson/lsdc_irq.h b/drivers/gpu/drm/loongson/lsdc_irq.h new file mode 100644 index 000000000000..528a74d9bf83 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_irq.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#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 diff --git a/drivers/gpu/drm/loongson/lsdc_output.c b/drivers/gpu/drm/loongson/lsdc_output.c new file mode 100644 index 000000000000..e8cefe6edd4d --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_output.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/gpu/drm/loongson/lsdc_output.h b/drivers/gpu/drm/loongson/lsdc_output.h new file mode 100644 index 000000000000..97ad9345ec10 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_output.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#ifndef __LSDC_OUTPUT_H__ +#define __LSDC_OUTPUT_H__ + +#include +#include + +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 diff --git a/drivers/gpu/drm/loongson/lsdc_pci_drv.c b/drivers/gpu/drm/loongson/lsdc_pci_drv.c new file mode 100644 index 000000000000..e202da6621bc --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_pci_drv.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * KMS driver for Loongson display controller + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#include +#include +#include +#include + +#include + +#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"); diff --git a/drivers/gpu/drm/loongson/lsdc_plane.c b/drivers/gpu/drm/loongson/lsdc_plane.c new file mode 100644 index 000000000000..6f65c9fd687e --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_plane.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/gpu/drm/loongson/lsdc_pll.c b/drivers/gpu/drm/loongson/lsdc_pll.c new file mode 100644 index 000000000000..4b3e7650c99a --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_pll.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#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); +} diff --git a/drivers/gpu/drm/loongson/lsdc_pll.h b/drivers/gpu/drm/loongson/lsdc_pll.h new file mode 100644 index 000000000000..2b9101f56097 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_pll.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#ifndef __LSDC_PLL_H__ +#define __LSDC_PLL_H__ + +#include + +/* + * 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 diff --git a/drivers/gpu/drm/loongson/lsdc_regs.h b/drivers/gpu/drm/loongson/lsdc_regs.h new file mode 100644 index 000000000000..ffa6285530d7 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_regs.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2022 Loongson Corporation + */ + +/* + * Authors: + * Sui Jingfeng + */ + +#ifndef __LSDC_REGS_H__ +#define __LSDC_REGS_H__ + +#include +#include + +/* + * 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 -- GitLab