diff options
32 files changed, 6491 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index afb3b2f5f425..cc9c9947fdef 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -323,6 +323,8 @@ source "drivers/gpu/drm/v3d/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" +source "drivers/gpu/drm/loongson/Kconfig" + source "drivers/gpu/drm/etnaviv/Kconfig" source "drivers/gpu/drm/hisilicon/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 7a09a89b493b..1c0f5204e47b 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -194,3 +194,4 @@ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ +obj-$(CONFIG_DRM_LOONGSON) += loongson/ diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig new file mode 100644 index 000000000000..df6946d505fa --- /dev/null +++ b/drivers/gpu/drm/loongson/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_LOONGSON + tristate "DRM support for Loongson Graphics" + depends on DRM && PCI && MMU + select DRM_KMS_HELPER + select DRM_TTM + select I2C + select I2C_ALGOBIT + help + This is a DRM driver for Loongson Graphics, it may including + LS7A2000, LS7A1000, LS2K2000 and LS2K1000 etc. Loongson LS7A + series are bridge chipset, while Loongson LS2K series are SoC. + + If "M" is selected, the module will be called loongson. + + If in doubt, say "N". diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile new file mode 100644 index 000000000000..91e72bd900c1 --- /dev/null +++ b/drivers/gpu/drm/loongson/Makefile @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 + +loongson-y := \ + lsdc_benchmark.o \ + lsdc_crtc.o \ + lsdc_debugfs.o \ + lsdc_drv.o \ + lsdc_gem.o \ + lsdc_gfxpll.o \ + lsdc_i2c.o \ + lsdc_irq.o \ + lsdc_output_7a1000.o \ + lsdc_output_7a2000.o \ + lsdc_plane.o \ + lsdc_pixpll.o \ + lsdc_probe.o \ + lsdc_ttm.o + +loongson-y += loongson_device.o \ + loongson_module.o + +obj-$(CONFIG_DRM_LOONGSON) += loongson.o diff --git a/drivers/gpu/drm/loongson/loongson_device.c b/drivers/gpu/drm/loongson/loongson_device.c new file mode 100644 index 000000000000..9986c8a2a255 --- /dev/null +++ b/drivers/gpu/drm/loongson/loongson_device.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/pci.h> + +#include "lsdc_drv.h" + +static const struct lsdc_kms_funcs ls7a1000_kms_funcs = { + .create_i2c = lsdc_create_i2c_chan, + .irq_handler = ls7a1000_dc_irq_handler, + .output_init = ls7a1000_output_init, + .cursor_plane_init = ls7a1000_cursor_plane_init, + .primary_plane_init = lsdc_primary_plane_init, + .crtc_init = ls7a1000_crtc_init, +}; + +static const struct lsdc_kms_funcs ls7a2000_kms_funcs = { + .create_i2c = lsdc_create_i2c_chan, + .irq_handler = ls7a2000_dc_irq_handler, + .output_init = ls7a2000_output_init, + .cursor_plane_init = ls7a2000_cursor_plane_init, + .primary_plane_init = lsdc_primary_plane_init, + .crtc_init = ls7a2000_crtc_init, +}; + +static const struct loongson_gfx_desc ls7a1000_gfx = { + .dc = { + .num_of_crtc = 2, + .max_pixel_clk = 200000, + .max_width = 2048, + .max_height = 2048, + .num_of_hw_cursor = 1, + .hw_cursor_w = 32, + .hw_cursor_h = 32, + .pitch_align = 256, + .has_vblank_counter = false, + .funcs = &ls7a1000_kms_funcs, + }, + .conf_reg_base = LS7A1000_CONF_REG_BASE, + .gfxpll = { + .reg_offset = LS7A1000_PLL_GFX_REG, + .reg_size = 8, + }, + .pixpll = { + [0] = { + .reg_offset = LS7A1000_PIXPLL0_REG, + .reg_size = 8, + }, + [1] = { + .reg_offset = LS7A1000_PIXPLL1_REG, + .reg_size = 8, + }, + }, + .chip_id = CHIP_LS7A1000, + .model = "LS7A1000 bridge chipset", +}; + +static const struct loongson_gfx_desc ls7a2000_gfx = { + .dc = { + .num_of_crtc = 2, + .max_pixel_clk = 350000, + .max_width = 4096, + .max_height = 4096, + .num_of_hw_cursor = 2, + .hw_cursor_w = 64, + .hw_cursor_h = 64, + .pitch_align = 64, + .has_vblank_counter = true, + .funcs = &ls7a2000_kms_funcs, + }, + .conf_reg_base = LS7A2000_CONF_REG_BASE, + .gfxpll = { + .reg_offset = LS7A2000_PLL_GFX_REG, + .reg_size = 8, + }, + .pixpll = { + [0] = { + .reg_offset = LS7A2000_PIXPLL0_REG, + .reg_size = 8, + }, + [1] = { + .reg_offset = LS7A2000_PIXPLL1_REG, + .reg_size = 8, + }, + }, + .chip_id = CHIP_LS7A2000, + .model = "LS7A2000 bridge chipset", +}; + +static const struct lsdc_desc *__chip_id_desc_table[] = { + [CHIP_LS7A1000] = &ls7a1000_gfx.dc, + [CHIP_LS7A2000] = &ls7a2000_gfx.dc, + [CHIP_LS_LAST] = NULL, +}; + +const struct lsdc_desc * +lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip_id) +{ + return __chip_id_desc_table[chip_id]; +} diff --git a/drivers/gpu/drm/loongson/loongson_module.c b/drivers/gpu/drm/loongson/loongson_module.c new file mode 100644 index 000000000000..d2a51bd395f6 --- /dev/null +++ b/drivers/gpu/drm/loongson/loongson_module.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/pci.h> + +#include <video/nomodeset.h> + +#include "loongson_module.h" + +static int loongson_modeset = -1; +MODULE_PARM_DESC(modeset, "Disable/Enable modesetting"); +module_param_named(modeset, loongson_modeset, int, 0400); + +int loongson_vblank = 1; +MODULE_PARM_DESC(vblank, "Disable/Enable hw vblank support"); +module_param_named(vblank, loongson_vblank, int, 0400); + +static int __init loongson_module_init(void) +{ + if (!loongson_modeset || video_firmware_drivers_only()) + return -ENODEV; + + return pci_register_driver(&lsdc_pci_driver); +} +module_init(loongson_module_init); + +static void __exit loongson_module_exit(void) +{ + pci_unregister_driver(&lsdc_pci_driver); +} +module_exit(loongson_module_exit); diff --git a/drivers/gpu/drm/loongson/loongson_module.h b/drivers/gpu/drm/loongson/loongson_module.h new file mode 100644 index 000000000000..931c17521bf0 --- /dev/null +++ b/drivers/gpu/drm/loongson/loongson_module.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LOONGSON_MODULE_H__ +#define __LOONGSON_MODULE_H__ + +extern int loongson_vblank; +extern struct pci_driver lsdc_pci_driver; + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_benchmark.c b/drivers/gpu/drm/loongson/lsdc_benchmark.c new file mode 100644 index 000000000000..b088646a2ff9 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_benchmark.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <drm/drm_debugfs.h> + +#include "lsdc_benchmark.h" +#include "lsdc_drv.h" +#include "lsdc_gem.h" +#include "lsdc_ttm.h" + +typedef void (*lsdc_copy_proc_t)(struct lsdc_bo *src_bo, + struct lsdc_bo *dst_bo, + unsigned int size, + int n); + +static void lsdc_copy_gtt_to_vram_cpu(struct lsdc_bo *src_bo, + struct lsdc_bo *dst_bo, + unsigned int size, + int n) +{ + lsdc_bo_kmap(src_bo); + lsdc_bo_kmap(dst_bo); + + while (n--) + memcpy_toio(dst_bo->kptr, src_bo->kptr, size); + + lsdc_bo_kunmap(src_bo); + lsdc_bo_kunmap(dst_bo); +} + +static void lsdc_copy_vram_to_gtt_cpu(struct lsdc_bo *src_bo, + struct lsdc_bo *dst_bo, + unsigned int size, + int n) +{ + lsdc_bo_kmap(src_bo); + lsdc_bo_kmap(dst_bo); + + while (n--) + memcpy_fromio(dst_bo->kptr, src_bo->kptr, size); + + lsdc_bo_kunmap(src_bo); + lsdc_bo_kunmap(dst_bo); +} + +static void lsdc_copy_gtt_to_gtt_cpu(struct lsdc_bo *src_bo, + struct lsdc_bo *dst_bo, + unsigned int size, + int n) +{ + lsdc_bo_kmap(src_bo); + lsdc_bo_kmap(dst_bo); + + while (n--) + memcpy(dst_bo->kptr, src_bo->kptr, size); + + lsdc_bo_kunmap(src_bo); + lsdc_bo_kunmap(dst_bo); +} + +static void lsdc_benchmark_copy(struct lsdc_device *ldev, + unsigned int size, + unsigned int n, + u32 src_domain, + u32 dst_domain, + lsdc_copy_proc_t copy_proc, + struct drm_printer *p) +{ + struct drm_device *ddev = &ldev->base; + struct lsdc_bo *src_bo; + struct lsdc_bo *dst_bo; + unsigned long start_jiffies; + unsigned long end_jiffies; + unsigned int throughput; + unsigned int time; + + src_bo = lsdc_bo_create_kernel_pinned(ddev, src_domain, size); + dst_bo = lsdc_bo_create_kernel_pinned(ddev, dst_domain, size); + + start_jiffies = jiffies; + + copy_proc(src_bo, dst_bo, size, n); + + end_jiffies = jiffies; + + lsdc_bo_free_kernel_pinned(src_bo); + lsdc_bo_free_kernel_pinned(dst_bo); + + time = jiffies_to_msecs(end_jiffies - start_jiffies); + + throughput = (n * (size >> 10)) / time; + + drm_printf(p, + "Copy bo of %uKiB %u times from %s to %s in %ums: %uMB/s\n", + size >> 10, n, + lsdc_domain_to_str(src_domain), + lsdc_domain_to_str(dst_domain), + time, throughput); +} + +int lsdc_show_benchmark_copy(struct lsdc_device *ldev, struct drm_printer *p) +{ + unsigned int buffer_size = 1920 * 1080 * 4; + unsigned int iteration = 60; + + lsdc_benchmark_copy(ldev, + buffer_size, + iteration, + LSDC_GEM_DOMAIN_GTT, + LSDC_GEM_DOMAIN_GTT, + lsdc_copy_gtt_to_gtt_cpu, + p); + + lsdc_benchmark_copy(ldev, + buffer_size, + iteration, + LSDC_GEM_DOMAIN_GTT, + LSDC_GEM_DOMAIN_VRAM, + lsdc_copy_gtt_to_vram_cpu, + p); + + lsdc_benchmark_copy(ldev, + buffer_size, + iteration, + LSDC_GEM_DOMAIN_VRAM, + LSDC_GEM_DOMAIN_GTT, + lsdc_copy_vram_to_gtt_cpu, + p); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_benchmark.h b/drivers/gpu/drm/loongson/lsdc_benchmark.h new file mode 100644 index 000000000000..36110278237e --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_benchmark.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_BENCHMARK_H__ +#define __LSDC_BENCHMARK_H__ + +#include "lsdc_drv.h" + +int lsdc_show_benchmark_copy(struct lsdc_device *ldev, struct drm_printer *p); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c new file mode 100644 index 000000000000..827acab580fa --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -0,0 +1,1024 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/delay.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_debugfs.h> +#include <drm/drm_vblank.h> + +#include "lsdc_drv.h" + +/* + * After the CRTC soft reset, the vblank counter would be reset to zero. + * But the address and other settings in the CRTC register remain the same + * as before. + */ + +static void lsdc_crtc0_soft_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + val &= CFG_VALID_BITS_MASK; + + /* Soft reset bit, active low */ + val &= ~CFG_RESET_N; + + val &= ~CFG_PIX_FMT_MASK; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + + udelay(1); + + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + + /* Wait about a vblank time */ + mdelay(20); +} + +static void lsdc_crtc1_soft_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + val &= CFG_VALID_BITS_MASK; + + /* Soft reset bit, active low */ + val &= ~CFG_RESET_N; + + val &= ~CFG_PIX_FMT_MASK; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + + udelay(1); + + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + + /* Wait about a vblank time */ + msleep(20); +} + +static void lsdc_crtc0_enable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + /* + * This may happen in extremely rare cases, but a soft reset can + * bring it back to normal. We add a warning here, hoping to catch + * something if it happens. + */ + if (val & CRTC_ANCHORED) { + drm_warn(&ldev->base, "%s stall\n", lcrtc->base.name); + return lsdc_crtc0_soft_reset(lcrtc); + } + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val | CFG_OUTPUT_ENABLE); +} + +static void lsdc_crtc0_disable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_CRTC0_CFG_REG, CFG_OUTPUT_ENABLE); + + udelay(9); +} + +static void lsdc_crtc1_enable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + /* + * This may happen in extremely rare cases, but a soft reset can + * bring it back to normal. We add a warning here, hoping to catch + * something if it happens. + */ + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (val & CRTC_ANCHORED) { + drm_warn(&ldev->base, "%s stall\n", lcrtc->base.name); + return lsdc_crtc1_soft_reset(lcrtc); + } + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val | CFG_OUTPUT_ENABLE); +} + +static void lsdc_crtc1_disable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_CRTC1_CFG_REG, CFG_OUTPUT_ENABLE); + + udelay(9); +} + +/* All Loongson display controllers have hardware scanout position recoders */ + +static void lsdc_crtc0_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); + + *hpos = val >> 16; + *vpos = val & 0xffff; +} + +static void lsdc_crtc1_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); + + *hpos = val >> 16; + *vpos = val & 0xffff; +} + +static void lsdc_crtc0_enable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); +} + +static void lsdc_crtc0_disable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); +} + +static void lsdc_crtc1_enable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); +} + +static void lsdc_crtc1_disable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); +} + +static void lsdc_crtc0_flip(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_PAGE_FLIP); +} + +static void lsdc_crtc1_flip(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_PAGE_FLIP); +} + +/* + * CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic + * This may be useful for custom cloning (TWIN) applications. Saving the + * bandwidth compared with the clone (mirroring) display mode provided by + * drm core. + */ + +static void lsdc_crtc0_clone(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_HW_CLONE); +} + +static void lsdc_crtc1_clone(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_HW_CLONE); +} + +static void lsdc_crtc0_set_mode(struct lsdc_crtc *lcrtc, + const struct drm_display_mode *mode) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); + + lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); +} + +static void lsdc_crtc1_set_mode(struct lsdc_crtc *lcrtc, + const struct drm_display_mode *mode) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); + + lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); +} + +/* + * This is required for S3 support. + * After resuming from suspend, LSDC_CRTCx_CFG_REG (x = 0 or 1) is filled + * with garbage value, which causes the CRTC hang there. + * + * This function provides minimal settings for the affected registers. + * This overrides the firmware's settings on startup, making the CRTC work + * on our own, similar to the functional of GPU POST (Power On Self Test). + * Only touch CRTC hardware-related parts. + */ + +static void lsdc_crtc0_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, CFG_RESET_N | LSDC_PF_XRGB8888); +} + +static void lsdc_crtc1_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, CFG_RESET_N | LSDC_PF_XRGB8888); +} + +static const struct lsdc_crtc_hw_ops ls7a1000_crtc_hw_ops[2] = { + { + .enable = lsdc_crtc0_enable, + .disable = lsdc_crtc0_disable, + .enable_vblank = lsdc_crtc0_enable_vblank, + .disable_vblank = lsdc_crtc0_disable_vblank, + .flip = lsdc_crtc0_flip, + .clone = lsdc_crtc0_clone, + .set_mode = lsdc_crtc0_set_mode, + .get_scan_pos = lsdc_crtc0_scan_pos, + .soft_reset = lsdc_crtc0_soft_reset, + .reset = lsdc_crtc0_reset, + }, + { + .enable = lsdc_crtc1_enable, + .disable = lsdc_crtc1_disable, + .enable_vblank = lsdc_crtc1_enable_vblank, + .disable_vblank = lsdc_crtc1_disable_vblank, + .flip = lsdc_crtc1_flip, + .clone = lsdc_crtc1_clone, + .set_mode = lsdc_crtc1_set_mode, + .get_scan_pos = lsdc_crtc1_scan_pos, + .soft_reset = lsdc_crtc1_soft_reset, + .reset = lsdc_crtc1_reset, + }, +}; + +/* + * The 32-bit hardware vblank counter has been available since LS7A2000 + * and LS2K2000. The counter increases even though the CRTC is disabled, + * it will be reset only if the CRTC is being soft reset. + * Those registers are also readable for ls7a1000, but its value does not + * change. + */ + +static u32 lsdc_crtc0_get_vblank_count(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); +} + +static u32 lsdc_crtc1_get_vblank_count(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); +} + +/* + * The DMA step bit fields are available since LS7A2000/LS2K2000, for + * supporting odd resolutions. But a large DMA step save the bandwidth. + * The larger, the better. Behavior of writing those bits on LS7A1000 + * or LS2K1000 is underfined. + */ + +static void lsdc_crtc0_set_dma_step(struct lsdc_crtc *lcrtc, + enum lsdc_dma_steps dma_step) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + val &= ~CFG_DMA_STEP_MASK; + val |= dma_step << CFG_DMA_STEP_SHIFT; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); +} + +static void lsdc_crtc1_set_dma_step(struct lsdc_crtc *lcrtc, + enum lsdc_dma_steps dma_step) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + val &= ~CFG_DMA_STEP_MASK; + val |= dma_step << CFG_DMA_STEP_SHIFT; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); +} + +static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = { + { + .enable = lsdc_crtc0_enable, + .disable = lsdc_crtc0_disable, + .enable_vblank = lsdc_crtc0_enable_vblank, + .disable_vblank = lsdc_crtc0_disable_vblank, + .flip = lsdc_crtc0_flip, + .clone = lsdc_crtc0_clone, + .set_mode = lsdc_crtc0_set_mode, + .soft_reset = lsdc_crtc0_soft_reset, + .get_scan_pos = lsdc_crtc0_scan_pos, + .set_dma_step = lsdc_crtc0_set_dma_step, + .get_vblank_counter = lsdc_crtc0_get_vblank_count, + .reset = lsdc_crtc0_reset, + }, + { + .enable = lsdc_crtc1_enable, + .disable = lsdc_crtc1_disable, + .enable_vblank = lsdc_crtc1_enable_vblank, + .disable_vblank = lsdc_crtc1_disable_vblank, + .flip = lsdc_crtc1_flip, + .clone = lsdc_crtc1_clone, + .set_mode = lsdc_crtc1_set_mode, + .get_scan_pos = lsdc_crtc1_scan_pos, + .soft_reset = lsdc_crtc1_soft_reset, + .set_dma_step = lsdc_crtc1_set_dma_step, + .get_vblank_counter = lsdc_crtc1_get_vblank_count, + .reset = lsdc_crtc1_reset, + }, +}; + +static void lsdc_crtc_reset(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + struct lsdc_crtc_state *priv_crtc_state; + + if (crtc->state) + crtc->funcs->atomic_destroy_state(crtc, crtc->state); + + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); + + if (!priv_crtc_state) + __drm_atomic_helper_crtc_reset(crtc, NULL); + else + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); + + /* Reset the CRTC hardware, this is required for S3 support */ + ops->reset(lcrtc); +} + +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(&priv_state->base); + + kfree(priv_state); +} + +static struct drm_crtc_state * +lsdc_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct lsdc_crtc_state *new_priv_state; + struct lsdc_crtc_state *old_priv_state; + + new_priv_state = kzalloc(sizeof(*new_priv_state), GFP_KERNEL); + if (!new_priv_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->base); + + old_priv_state = to_lsdc_crtc_state(crtc->state); + + memcpy(&new_priv_state->pparms, &old_priv_state->pparms, + sizeof(new_priv_state->pparms)); + + return &new_priv_state->base; +} + +static u32 lsdc_crtc_get_vblank_counter(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + /* 32-bit hardware vblank counter */ + return lcrtc->hw_ops->get_vblank_counter(lcrtc); +} + +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + if (!lcrtc->has_vblank) + return -EINVAL; + + lcrtc->hw_ops->enable_vblank(lcrtc); + + return 0; +} + +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + if (!lcrtc->has_vblank) + return; + + lcrtc->hw_ops->disable_vblank(lcrtc); +} + +/* + * CRTC related debugfs + * Primary planes and cursor planes belong to the CRTC as well. + * For the sake of convenience, plane-related registers are also add here. + */ + +#define REG_DEF(reg) { \ + .name = __stringify_1(LSDC_##reg##_REG), \ + .offset = LSDC_##reg##_REG, \ +} + +static const struct lsdc_reg32 lsdc_crtc_regs_array[2][21] = { + [0] = { + REG_DEF(CRTC0_CFG), + REG_DEF(CRTC0_FB_ORIGIN), + REG_DEF(CRTC0_DVO_CONF), + REG_DEF(CRTC0_HDISPLAY), + REG_DEF(CRTC0_HSYNC), + REG_DEF(CRTC0_VDISPLAY), + REG_DEF(CRTC0_VSYNC), + REG_DEF(CRTC0_GAMMA_INDEX), + REG_DEF(CRTC0_GAMMA_DATA), + REG_DEF(CRTC0_SYNC_DEVIATION), + REG_DEF(CRTC0_VSYNC_COUNTER), + REG_DEF(CRTC0_SCAN_POS), + REG_DEF(CRTC0_STRIDE), + REG_DEF(CRTC0_FB1_ADDR_HI), + REG_DEF(CRTC0_FB1_ADDR_LO), + REG_DEF(CRTC0_FB0_ADDR_HI), + REG_DEF(CRTC0_FB0_ADDR_LO), + REG_DEF(CURSOR0_CFG), + REG_DEF(CURSOR0_POSITION), + REG_DEF(CURSOR0_BG_COLOR), + REG_DEF(CURSOR0_FG_COLOR), + }, + [1] = { + REG_DEF(CRTC1_CFG), + REG_DEF(CRTC1_FB_ORIGIN), + REG_DEF(CRTC1_DVO_CONF), + REG_DEF(CRTC1_HDISPLAY), + REG_DEF(CRTC1_HSYNC), + REG_DEF(CRTC1_VDISPLAY), + REG_DEF(CRTC1_VSYNC), + REG_DEF(CRTC1_GAMMA_INDEX), + REG_DEF(CRTC1_GAMMA_DATA), + REG_DEF(CRTC1_SYNC_DEVIATION), + REG_DEF(CRTC1_VSYNC_COUNTER), + REG_DEF(CRTC1_SCAN_POS), + REG_DEF(CRTC1_STRIDE), + REG_DEF(CRTC1_FB1_ADDR_HI), + REG_DEF(CRTC1_FB1_ADDR_LO), + REG_DEF(CRTC1_FB0_ADDR_HI), + REG_DEF(CRTC1_FB0_ADDR_LO), + REG_DEF(CURSOR1_CFG), + REG_DEF(CURSOR1_POSITION), + REG_DEF(CURSOR1_BG_COLOR), + REG_DEF(CURSOR1_FG_COLOR), + }, +}; + |
