diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/gpu/drm/loongson | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/loongson')
30 files changed, 6486 insertions, 0 deletions
diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig new file mode 100644 index 0000000000..df6946d505 --- /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 0000000000..91e72bd900 --- /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 0000000000..9986c8a2a2 --- /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 0000000000..d2a51bd395 --- /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 0000000000..931c17521b --- /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 0000000000..b088646a2f --- /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 0000000000..3611027823 --- /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 0000000000..827acab580 --- /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), + }, +}; + +static int lsdc_crtc_show_regs(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + struct lsdc_device *ldev = lcrtc->ldev; + unsigned int i; + + for (i = 0; i < lcrtc->nreg; i++) { + const struct lsdc_reg32 *preg = &lcrtc->preg[i]; + u32 offset = preg->offset; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + preg->name, offset, lsdc_rreg32(ldev, offset)); + } + + return 0; +} + +static int lsdc_crtc_show_scan_position(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + int x, y; + + lcrtc->hw_ops->get_scan_pos(lcrtc, &x, &y); + seq_printf(m, "Scanout position: x: %08u, y: %08u\n", x, y); + + return 0; +} + +static int lsdc_crtc_show_vblank_counter(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + + if (lcrtc->hw_ops->get_vblank_counter) + seq_printf(m, "%s vblank counter: %08u\n\n", lcrtc->base.name, + lcrtc->hw_ops->get_vblank_counter(lcrtc)); + + return 0; +} + +static int lsdc_pixpll_show_clock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; + struct drm_crtc *crtc = &lcrtc->base; + struct drm_display_mode *mode = &crtc->state->mode; + struct drm_printer printer = drm_seq_file_printer(m); + unsigned int out_khz; + + out_khz = funcs->get_rate(pixpll); + + seq_printf(m, "%s: %dx%d@%d\n", crtc->name, + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode)); + + seq_printf(m, "Pixel clock required: %d kHz\n", mode->clock); + seq_printf(m, "Actual frequency output: %u kHz\n", out_khz); + seq_printf(m, "Diff: %d kHz\n", out_khz - mode->clock); + + funcs->print(pixpll, &printer); + + return 0; +} + +static struct drm_info_list lsdc_crtc_debugfs_list[2][4] = { + [0] = { + { "regs", lsdc_crtc_show_regs, 0, NULL }, + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, + }, + [1] = { + { "regs", lsdc_crtc_show_regs, 0, NULL }, + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, + }, +}; + +/* operate manually */ + +static int lsdc_crtc_man_op_show(struct seq_file *m, void *data) +{ + seq_puts(m, "soft_reset: soft reset this CRTC\n"); + seq_puts(m, "enable: enable this CRTC\n"); + seq_puts(m, "disable: disable this CRTC\n"); + seq_puts(m, "flip: trigger the page flip\n"); + seq_puts(m, "clone: clone the another crtc with hardware logic\n"); + + return 0; +} + +static int lsdc_crtc_man_op_open(struct inode *inode, struct file *file) +{ + struct drm_crtc *crtc = inode->i_private; + + return single_open(file, lsdc_crtc_man_op_show, crtc); +} + +static ssize_t lsdc_crtc_man_op_write(struct file *file, + const char __user *ubuf, + size_t len, + loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct lsdc_crtc *lcrtc = m->private; + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + char buf[16]; + + if (len > sizeof(buf) - 1) + return -EINVAL; + + if (copy_from_user(buf, ubuf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sysfs_streq(buf, "soft_reset")) + ops->soft_reset(lcrtc); + else if (sysfs_streq(buf, "enable")) + ops->enable(lcrtc); + else if (sysfs_streq(buf, "disable")) + ops->disable(lcrtc); + else if (sysfs_streq(buf, "flip")) + ops->flip(lcrtc); + else if (sysfs_streq(buf, "clone")) + ops->clone(lcrtc); + + return len; +} + +static const struct file_operations lsdc_crtc_man_op_fops = { + .owner = THIS_MODULE, + .open = lsdc_crtc_man_op_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = lsdc_crtc_man_op_write, +}; + +static int lsdc_crtc_late_register(struct drm_crtc *crtc) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + struct drm_minor *minor = crtc->dev->primary; + unsigned int index = dispipe->index; + unsigned int i; + + lcrtc->preg = lsdc_crtc_regs_array[index]; + lcrtc->nreg = ARRAY_SIZE(lsdc_crtc_regs_array[index]); + lcrtc->p_info_list = lsdc_crtc_debugfs_list[index]; + lcrtc->n_info_list = ARRAY_SIZE(lsdc_crtc_debugfs_list[index]); + + for (i = 0; i < lcrtc->n_info_list; ++i) + lcrtc->p_info_list[i].data = lcrtc; + + drm_debugfs_create_files(lcrtc->p_info_list, lcrtc->n_info_list, + crtc->debugfs_entry, minor); + + /* Manual operations supported */ + debugfs_create_file("ops", 0644, crtc->debugfs_entry, lcrtc, + &lsdc_crtc_man_op_fops); + + return 0; +} + +static void lsdc_crtc_atomic_print_state(struct drm_printer *p, + const struct drm_crtc_state *state) +{ + const struct lsdc_crtc_state *priv_state; + const struct lsdc_pixpll_parms *pparms; + + priv_state = container_of_const(state, struct lsdc_crtc_state, base); + pparms = &priv_state->pparms; + + drm_printf(p, "\tInput clock divider = %u\n", pparms->div_ref); + drm_printf(p, "\tMedium clock multiplier = %u\n", pparms->loopc); + drm_printf(p, "\tOutput clock divider = %u\n", pparms->div_out); +} + +static const struct drm_crtc_funcs ls7a1000_crtc_funcs = { + .reset = lsdc_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, + .late_register = lsdc_crtc_late_register, + .enable_vblank = lsdc_crtc_enable_vblank, + .disable_vblank = lsdc_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .atomic_print_state = lsdc_crtc_atomic_print_state, +}; + +static const struct drm_crtc_funcs ls7a2000_crtc_funcs = { + .reset = lsdc_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, + .late_register = lsdc_crtc_late_register, + .get_vblank_counter = lsdc_crtc_get_vblank_counter, + .enable_vblank = lsdc_crtc_enable_vblank, + .disable_vblank = lsdc_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .atomic_print_state = lsdc_crtc_atomic_print_state, +}; + +static enum drm_mode_status +lsdc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + unsigned int pitch; + + if (mode->hdisplay > descp->max_width) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > descp->max_height) + return MODE_BAD_VVALUE; + + if (mode->clock > descp->max_pixel_clk) { + drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n", + mode->hdisplay, mode->vdisplay, mode->clock); + return MODE_CLOCK_HIGH; + } + + /* 4 for DRM_FORMAT_XRGB8888 */ + pitch = mode->hdisplay * 4; + + if (pitch % descp->pitch_align) { + drm_dbg_kms(ddev, "align to %u bytes is required: %u\n", + descp->pitch_align, pitch); + return MODE_BAD_WIDTH; + } + + return MODE_OK; +} + +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + unsigned int clock = state->mode.clock; + int ret; + + ret = pfuncs->compute(pixpll, clock, &priv_state->pparms); + if (ret) { + drm_warn(crtc->dev, "Failed to find PLL params for %ukHz\n", + clock); + return -EINVAL; + } + + return 0; +} + +static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->enable) + return 0; + + return lsdc_pixpll_atomic_check(crtc, crtc_state); +} + +static void lsdc_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *crtc_hw_ops = lcrtc->hw_ops; + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *pixpll_funcs = pixpll->funcs; + struct drm_crtc_state *state = crtc->state; + struct drm_display_mode *mode = &state->mode; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + + pixpll_funcs->update(pixpll, &priv_state->pparms); + + if (crtc_hw_ops->set_dma_step) { + unsigned int width_in_bytes = mode->hdisplay * 4; + enum lsdc_dma_steps dma_step; + + /* + * Using DMA step as large as possible, for improving + * hardware DMA efficiency. + */ + if (width_in_bytes % 256 == 0) + dma_step = LSDC_DMA_STEP_256_BYTES; + else if (width_in_bytes % 128 == 0) + dma_step = LSDC_DMA_STEP_128_BYTES; + else if (width_in_bytes % 64 == 0) + dma_step = LSDC_DMA_STEP_64_BYTES; + else /* width_in_bytes % 32 == 0 */ + dma_step = LSDC_DMA_STEP_32_BYTES; + + crtc_hw_ops->set_dma_step(lcrtc, dma_step); + } + + crtc_hw_ops->set_mode(lcrtc, mode); +} + +static void lsdc_crtc_send_vblank(struct drm_crtc *crtc) +{ + struct drm_device *ddev = crtc->dev; + unsigned long flags; + + if (!crtc->state || !crtc->state->event) + return; + + drm_dbg(ddev, "Send vblank manually\n"); + + spin_lock_irqsave(&ddev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&ddev->event_lock, flags); +} + +static void lsdc_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + if (lcrtc->has_vblank) + drm_crtc_vblank_on(crtc); + + lcrtc->hw_ops->enable(lcrtc); +} + +static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + + if (lcrtc->has_vblank) + drm_crtc_vblank_off(crtc); + + lcrtc->hw_ops->disable(lcrtc); + + /* + * Make sure we issue a vblank event after disabling the CRTC if + * someone was waiting it. + */ + lsdc_crtc_send_vblank(crtc); +} + +static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + else + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static bool lsdc_crtc_get_scanout_position(struct drm_crtc *crtc, + bool in_vblank_irq, + int *vpos, + int *hpos, + ktime_t *stime, + ktime_t *etime, + const struct drm_display_mode *mode) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + int vsw, vbp, vactive_start, vactive_end, vfp_end; + int x, y; + + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; + + vactive_start = vsw + vbp + 1; + vactive_end = vactive_start + mode->crtc_vdisplay; + + /* last scan line before VSYNC */ + vfp_end = mode->crtc_vtotal; + + if (stime) + *stime = ktime_get(); + + ops->get_scan_pos(lcrtc, &x, &y); + + if (y > vactive_end) + y = y - vfp_end - vactive_start; + else + y -= vactive_start; + + *vpos = y; + *hpos = 0; + + if (etime) + *etime = ktime_get(); + + return true; +} + +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { + .mode_valid = lsdc_crtc_mode_valid, + .mode_set_nofb = lsdc_crtc_mode_set_nofb, + .atomic_enable = lsdc_crtc_atomic_enable, + .atomic_disable = lsdc_crtc_atomic_disable, + .atomic_check = lsdc_crtc_helper_atomic_check, + .atomic_flush = lsdc_crtc_atomic_flush, + .get_scanout_position = lsdc_crtc_get_scanout_position, +}; + +int ls7a1000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index, + bool has_vblank) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + int ret; + + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); + if (ret) { + drm_err(ddev, "pixel pll init failed: %d\n", ret); + return ret; + } + + lcrtc->ldev = to_lsdc(ddev); + lcrtc->has_vblank = has_vblank; + lcrtc->hw_ops = &ls7a1000_crtc_hw_ops[index]; + + ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, + &ls7a1000_crtc_funcs, + "LS-CRTC-%d", index); + if (ret) { + drm_err(ddev, "crtc init with planes failed: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); + + ret = drm_mode_crtc_set_gamma_size(crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); + + return 0; +} + +int ls7a2000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index, + bool has_vblank) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + int ret; + + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); + if (ret) { + drm_err(ddev, "crtc init with pll failed: %d\n", ret); + return ret; + } + + lcrtc->ldev = to_lsdc(ddev); + lcrtc->has_vblank = has_vblank; + lcrtc->hw_ops = &ls7a2000_crtc_hw_ops[index]; + + ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, + &ls7a2000_crtc_funcs, + "LS-CRTC-%u", index); + if (ret) { + drm_err(ddev, "crtc init with planes failed: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); + + ret = drm_mode_crtc_set_gamma_size(crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_debugfs.c b/drivers/gpu/drm/loongson/lsdc_debugfs.c new file mode 100644 index 0000000000..b9c2e6b170 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.c @@ -0,0 +1,110 @@ +// 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_probe.h" +#include "lsdc_ttm.h" + +/* device level debugfs */ + +static int lsdc_identify(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp); + u8 impl, rev; + + loongson_cpu_get_prid(&impl, &rev); + + seq_printf(m, "Running on cpu 0x%x, cpu revision: 0x%x\n", + impl, rev); + + seq_printf(m, "Contained in: %s\n", gfx->model); + + 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; +} + +static int lsdc_show_gfxpll_clock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + struct drm_printer printer = drm_seq_file_printer(m); + struct loongson_gfxpll *gfxpll = ldev->gfxpll; + + gfxpll->funcs->print(gfxpll, &printer, true); + + return 0; +} + +static int lsdc_show_benchmark(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + struct drm_printer printer = drm_seq_file_printer(m); + + lsdc_show_benchmark_copy(ldev, &printer); + + return 0; +} + +static int lsdc_pdev_enable_io_mem(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + u16 cmd; + + pci_read_config_word(ldev->dc, PCI_COMMAND, &cmd); + + seq_printf(m, "PCI_COMMAND: 0x%x\n", cmd); + + cmd |= PCI_COMMAND_MEMORY | PCI_COMMAND_IO; + + pci_write_config_word(ldev->dc, PCI_COMMAND, cmd); + + pci_read_config_word(ldev->dc, PCI_COMMAND, &cmd); + + seq_printf(m, "PCI_COMMAND: 0x%x\n", cmd); + + return 0; +} + +static struct drm_info_list lsdc_debugfs_list[] = { + { "benchmark", lsdc_show_benchmark, 0, NULL }, + { "bos", lsdc_show_buffer_object, 0, NULL }, + { "chips", lsdc_identify, 0, NULL }, + { "clocks", lsdc_show_gfxpll_clock, 0, NULL }, + { "dc_enable", lsdc_pdev_enable_io_mem, 0, NULL }, + { "mm", lsdc_show_mm, 0, NULL }, +}; + +void lsdc_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *ddev = minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int n = ARRAY_SIZE(lsdc_debugfs_list); + unsigned int i; + + for (i = 0; i < n; ++i) + lsdc_debugfs_list[i].data = ldev; + + drm_debugfs_create_files(lsdc_debugfs_list, n, minor->debugfs_root, minor); + + lsdc_ttm_debugfs_init(ldev); +} diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c new file mode 100644 index 0000000000..188ec82afc --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/pci.h> +#include <linux/vgaarb.h> + +#include <drm/drm_aperture.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_generic.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_ioctl.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "loongson_module.h" +#include "lsdc_drv.h" +#include "lsdc_gem.h" +#include "lsdc_ttm.h" + +#define DRIVER_AUTHOR "Sui Jingfeng <suijingfeng@loongson.cn>" +#define DRIVER_NAME "loongson" +#define DRIVER_DESC "drm driver for loongson graphics" +#define DRIVER_DATE "20220701" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); + +static const struct drm_driver lsdc_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_RENDER | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &lsdc_gem_fops, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + .debugfs_init = lsdc_debugfs_init, + .dumb_create = lsdc_dumb_create, + .dumb_map_offset = lsdc_dumb_map_offset, + .gem_prime_import_sg_table = lsdc_prime_import_sg_table, +}; + +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +/* Display related */ + +static int lsdc_modeset_init(struct lsdc_device *ldev, + unsigned int num_crtc, + const struct lsdc_kms_funcs *funcs, + bool has_vblank) +{ + struct drm_device *ddev = &ldev->base; + struct lsdc_display_pipe *dispipe; + unsigned int i; + int ret; + + for (i = 0; i < num_crtc; i++) { + dispipe = &ldev->dispipe[i]; + + /* We need an index before crtc is initialized */ + dispipe->index = i; + + ret = funcs->create_i2c(ddev, dispipe, i); + if (ret) + return ret; + } + + for (i = 0; i < num_crtc; i++) { + struct i2c_adapter *ddc = NULL; + + dispipe = &ldev->dispipe[i]; + if (dispipe->li2c) + ddc = &dispipe->li2c->adapter; + + ret = funcs->output_init(ddev, dispipe, ddc, i); + if (ret) + return ret; + + ldev->num_output++; + } + + for (i = 0; i < num_crtc; i++) { + dispipe = &ldev->dispipe[i]; + + ret = funcs->primary_plane_init(ddev, &dispipe->primary.base, i); + if (ret) + return ret; + + ret = funcs->cursor_plane_init(ddev, &dispipe->cursor.base, i); + if (ret) + return ret; + + ret = funcs->crtc_init(ddev, &dispipe->crtc.base, + &dispipe->primary.base, + &dispipe->cursor.base, + i, has_vblank); + if (ret) + return ret; + } + + drm_info(ddev, "Total %u outputs\n", ldev->num_output); + + return 0; +} + +static const struct drm_mode_config_helper_funcs lsdc_mode_config_helper_funcs = { + .atomic_commit_tail = drm_atomic_helper_commit_tail, +}; + +static int lsdc_mode_config_init(struct drm_device *ddev, + const struct lsdc_desc *descp) +{ + 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 = descp->max_width * LSDC_NUM_CRTC; + ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; + ddev->mode_config.preferred_depth = 24; + ddev->mode_config.prefer_shadow = 1; + + ddev->mode_config.cursor_width = descp->hw_cursor_h; + ddev->mode_config.cursor_height = descp->hw_cursor_h; + + ddev->mode_config.helper_private = &lsdc_mode_config_helper_funcs; + + if (descp->has_vblank_counter) + ddev->max_vblank_count = 0xffffffff; + + return ret; +} + +/* + * The GPU and display controller in the LS7A1000/LS7A2000/LS2K2000 are + * separated PCIE devices. They are two devices, not one. Bar 2 of the GPU + * device contains the base address and size of the VRAM, both the GPU and + * the DC could access the on-board VRAM. + */ +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, + struct pci_dev *pdev_dc, + const struct lsdc_desc *descp) +{ + struct drm_device *ddev = &ldev->base; + struct pci_dev *pdev_gpu; + resource_size_t base, size; + + /* + * The GPU has 00:06.0 as its BDF, while the DC has 00:06.1 + * This is true for the LS7A1000, LS7A2000 and LS2K2000. + */ + pdev_gpu = pci_get_domain_bus_and_slot(pci_domain_nr(pdev_dc->bus), + pdev_dc->bus->number, + PCI_DEVFN(6, 0)); + if (!pdev_gpu) { + drm_err(ddev, "No GPU device, then no VRAM\n"); + return -ENODEV; + } + + base = pci_resource_start(pdev_gpu, 2); + size = pci_resource_len(pdev_gpu, 2); + + ldev->vram_base = base; + ldev->vram_size = size; + ldev->gpu = pdev_gpu; + + drm_info(ddev, "Dedicated vram start: 0x%llx, size: %uMiB\n", + (u64)base, (u32)(size >> 20)); + + return 0; +} + +static struct lsdc_device * +lsdc_create_device(struct pci_dev *pdev, + const struct lsdc_desc *descp, + const struct drm_driver *driver) +{ + struct lsdc_device *ldev; + struct drm_device *ddev; + int ret; + + ldev = devm_drm_dev_alloc(&pdev->dev, driver, struct lsdc_device, base); + if (IS_ERR(ldev)) + return ldev; + + ldev->dc = pdev; + ldev->descp = descp; + + ddev = &ldev->base; + + loongson_gfxpll_create(ddev, &ldev->gfxpll); + + ret = lsdc_get_dedicated_vram(ldev, pdev, descp); + if (ret) { + drm_err(ddev, "Init VRAM failed: %d\n", ret); + return ERR_PTR(ret); + } + + ret = drm_aperture_remove_conflicting_framebuffers(ldev->vram_base, + ldev->vram_size, + driver); + if (ret) { + drm_err(ddev, "Remove firmware framebuffers failed: %d\n", ret); + return ERR_PTR(ret); + } + + ret = lsdc_ttm_init(ldev); + if (ret) { + drm_err(ddev, "Memory manager init failed: %d\n", ret); + return ERR_PTR(ret); + } + + lsdc_gem_init(ddev); + + /* Bar 0 of the DC device contains the MMIO register's base address */ + ldev->reg_base = pcim_iomap(pdev, 0, 0); + if (!ldev->reg_base) + return ERR_PTR(-ENODEV); + + spin_lock_init(&ldev->reglock); + + ret = lsdc_mode_config_init(ddev, descp); + if (ret) + return ERR_PTR(ret); + + ret = lsdc_modeset_init(ldev, descp->num_of_crtc, descp->funcs, + loongson_vblank); + if (ret) + return ERR_PTR(ret); + + drm_mode_config_reset(ddev); + + return ldev; +} + +/* For multiple GPU driver instance co-exixt in the system */ + +static unsigned int lsdc_vga_set_decode(struct pci_dev *pdev, bool state) +{ + return VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM; +} + +static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + const struct lsdc_desc *descp; + struct drm_device *ddev; + struct lsdc_device *ldev; + int ret; + + descp = lsdc_device_probe(pdev, ent->driver_data); + if (IS_ERR_OR_NULL(descp)) + return -ENODEV; + + pci_set_master(pdev); + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); + if (ret) + return ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + dev_info(&pdev->dev, "Found %s, revision: %u", + to_loongson_gfx(descp)->model, pdev->revision); + + ldev = lsdc_create_device(pdev, descp, &lsdc_drm_driver); + if (IS_ERR(ldev)) + return PTR_ERR(ldev); + + ddev = &ldev->base; + + pci_set_drvdata(pdev, ddev); + + vga_client_register(pdev, lsdc_vga_set_decode); + + drm_kms_helper_poll_init(ddev); + + if (loongson_vblank) { + ret = drm_vblank_init(ddev, descp->num_of_crtc); + if (ret) + return ret; + + ret = devm_request_irq(&pdev->dev, pdev->irq, + descp->funcs->irq_handler, + IRQF_SHARED, + dev_name(&pdev->dev), ddev); + if (ret) { + drm_err(ddev, "Failed to register interrupt: %d\n", ret); + return ret; + } + + drm_info(ddev, "registered irq: %u\n", pdev->irq); + } + + ret = drm_dev_register(ddev, 0); + if (ret) + return ret; + + drm_fbdev_generic_setup(ddev, 32); + + return 0; +} + +static void lsdc_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *ddev = pci_get_drvdata(pdev); + + drm_dev_unregister(ddev); + drm_atomic_helper_shutdown(ddev); +} + +static int lsdc_drm_freeze(struct drm_device *ddev) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_bo *lbo; + int ret; + + /* unpin all of buffers in the VRAM */ + mutex_lock(&ldev->gem.mutex); + list_for_each_entry(lbo, &ldev->gem.objects, list) { + struct ttm_buffer_object *tbo = &lbo->tbo; + struct ttm_resource *resource = tbo->resource; + unsigned int pin_count = tbo->pin_count; + + drm_dbg(ddev, "bo[%p], size: %zuKiB, type: %s, pin count: %u\n", + lbo, lsdc_bo_size(lbo) >> 10, + lsdc_mem_type_to_str(resource->mem_type), pin_count); + + if (!pin_count) + continue; + + if (resource->mem_type == TTM_PL_VRAM) { + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) { + drm_err(ddev, "bo reserve failed: %d\n", ret); + continue; + } + + do { + lsdc_bo_unpin(lbo); + --pin_count; + } while (pin_count); + + lsdc_bo_unreserve(lbo); + } + } + mutex_unlock(&ldev->gem.mutex); + + lsdc_bo_evict_vram(ddev); + + ret = drm_mode_config_helper_suspend(ddev); + if (unlikely(ret)) { + drm_err(ddev, "Freeze error: %d", ret); + return ret; + } + + return 0; +} + +static int lsdc_drm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return drm_mode_config_helper_resume(ddev); +} + +static int lsdc_pm_freeze(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return lsdc_drm_freeze(ddev); +} + +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); + + pci_set_power_state(pdev, PCI_D0); + + pci_restore_state(pdev); + + if (pcim_enable_device(pdev)) + return -EIO; + + 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_VDEVICE(LOONGSON, 0x7a06), CHIP_LS7A1000}, + {PCI_VDEVICE(LOONGSON, 0x7a36), CHIP_LS7A2000}, + { } +}; + +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, +}; + +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/loongson/lsdc_drv.h b/drivers/gpu/drm/loongson/lsdc_drv.h new file mode 100644 index 0000000000..fbf2d760ef --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.h @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_DRV_H__ +#define __LSDC_DRV_H__ + +#include <linux/pci.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_file.h> +#include <drm/drm_plane.h> +#include <drm/ttm/ttm_device.h> + +#include "lsdc_i2c.h" +#include "lsdc_irq.h" +#include "lsdc_gfxpll.h" +#include "lsdc_output.h" +#include "lsdc_pixpll.h" +#include "lsdc_regs.h" + +/* Currently, all Loongson display controllers have two display pipes. */ +#define LSDC_NUM_CRTC 2 + +/* + * LS7A1000/LS7A2000 chipsets function as the south & north bridges of the + * Loongson 3 series processors, they are equipped with on-board video RAM + * typically. While Loongson LS2K series are low cost SoCs which share the + * system RAM as video RAM, they don't has a dedicated VRAM. + * + * There is only a 1:1 mapping of crtcs, encoders and connectors for the DC + * + * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 + cursor0 + primary0 + * display pipe 1 = crtc1 + dvo1 + encoder1 + connectro1 + cursor1 + primary1 + */ + +enum loongson_chip_id { + CHIP_LS7A1000 = 0, + CHIP_LS7A2000 = 1, + CHIP_LS_LAST, +}; + +const struct lsdc_desc * +lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip); + +struct lsdc_kms_funcs; + +/* DC specific */ + +struct lsdc_desc { + 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; + u32 pitch_align; /* CRTC DMA alignment constraint */ + bool has_vblank_counter; /* 32 bit hw vsync counter */ + + /* device dependent ops, dc side */ + const struct lsdc_kms_funcs *funcs; +}; + +/* GFX related resources wrangler */ + +struct loongson_gfx_desc { + struct lsdc_desc dc; + + u32 conf_reg_base; + + /* GFXPLL shared by the DC, GMC and GPU */ + struct { + u32 reg_offset; + u32 reg_size; + } gfxpll; + + /* Pixel PLL, per display pipe */ + struct { + u32 reg_offset; + u32 reg_size; + } pixpll[LSDC_NUM_CRTC]; + + enum loongson_chip_id chip_id; + char model[64]; +}; + +static inline const struct loongson_gfx_desc * +to_loongson_gfx(const struct lsdc_desc *dcp) +{ + return container_of_const(dcp, struct loongson_gfx_desc, dc); +}; + +struct lsdc_reg32 { + char *name; + u32 offset; +}; + +/* crtc hardware related ops */ + +struct lsdc_crtc; + +struct lsdc_crtc_hw_ops { + void (*enable)(struct lsdc_crtc *lcrtc); + void (*disable)(struct lsdc_crtc *lcrtc); + void (*enable_vblank)(struct lsdc_crtc *lcrtc); + void (*disable_vblank)(struct lsdc_crtc *lcrtc); + void (*flip)(struct lsdc_crtc *lcrtc); + void (*clone)(struct lsdc_crtc *lcrtc); + void (*get_scan_pos)(struct lsdc_crtc *lcrtc, int *hpos, int *vpos); + void (*set_mode)(struct lsdc_crtc *lcrtc, const struct drm_display_mode *mode); + void (*soft_reset)(struct lsdc_crtc *lcrtc); + void (*reset)(struct lsdc_crtc *lcrtc); + + u32 (*get_vblank_counter)(struct lsdc_crtc *lcrtc); + void (*set_dma_step)(struct lsdc_crtc *lcrtc, enum lsdc_dma_steps step); +}; + +struct lsdc_crtc { + struct drm_crtc base; + struct lsdc_pixpll pixpll; + struct lsdc_device *ldev; + const struct lsdc_crtc_hw_ops *hw_ops; + const struct lsdc_reg32 *preg; + unsigned int nreg; + struct drm_info_list *p_info_list; + unsigned int n_info_list; + bool has_vblank; +}; + +/* primary plane hardware related ops */ + +struct lsdc_primary; + +struct lsdc_primary_plane_ops { + void (*update_fb_addr)(struct lsdc_primary *plane, u64 addr); + void (*update_fb_stride)(struct lsdc_primary *plane, u32 stride); + void (*update_fb_format)(struct lsdc_primary *plane, + const struct drm_format_info *format); +}; + +struct lsdc_primary { + struct drm_plane base; + const struct lsdc_primary_plane_ops *ops; + struct lsdc_device *ldev; +}; + +/* cursor plane hardware related ops */ + +struct lsdc_cursor; + +struct lsdc_cursor_plane_ops { + void (*update_bo_addr)(struct lsdc_cursor *plane, u64 addr); + void (*update_cfg)(struct lsdc_cursor *plane, + enum lsdc_cursor_size cursor_size, + enum lsdc_cursor_format); + void (*update_position)(struct lsdc_cursor *plane, int x, int y); +}; + +struct lsdc_cursor { + struct drm_plane base; + const struct lsdc_cursor_plane_ops *ops; + struct lsdc_device *ldev; +}; + +struct lsdc_output { + struct drm_encoder encoder; + struct drm_connector connector; +}; + +static inline struct lsdc_output * +connector_to_lsdc_output(struct drm_connector *connector) +{ + return container_of(connector, struct lsdc_output, connector); +} + +static inline struct lsdc_output * +encoder_to_lsdc_output(struct drm_encoder *encoder) +{ + return container_of(encoder, struct lsdc_output, encoder); +} + +struct lsdc_display_pipe { + struct lsdc_crtc crtc; + struct lsdc_primary primary; + struct lsdc_cursor cursor; + struct lsdc_output output; + struct lsdc_i2c *li2c; + unsigned int index; +}; + +static inline struct lsdc_display_pipe * +output_to_display_pipe(struct lsdc_output *output) +{ + return container_of(output, struct lsdc_display_pipe, output); +} + +struct lsdc_kms_funcs { + irqreturn_t (*irq_handler)(int irq, void *arg); + + int (*create_i2c)(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + unsigned int index); + + int (*output_init)(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int index); + + int (*cursor_plane_init)(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index); + + int (*primary_plane_init)(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index); + + int (*crtc_init)(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index, + bool has_vblank); +}; + +static inline struct lsdc_crtc * +to_lsdc_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct lsdc_crtc, base); +} + +static inline struct lsdc_display_pipe * +crtc_to_display_pipe(struct drm_crtc *crtc) +{ + return container_of(crtc, struct lsdc_display_pipe, crtc.base); +} + +static inline struct lsdc_primary * +to_lsdc_primary(struct drm_plane *plane) +{ + return container_of(plane, struct lsdc_primary, base); +} + +static inline struct lsdc_cursor * +to_lsdc_cursor(struct drm_plane *plane) +{ + return container_of(plane, struct lsdc_cursor, base); +} + +struct lsdc_crtc_state { + struct drm_crtc_state base; + struct lsdc_pixpll_parms pparms; +}; + +struct lsdc_gem { + /* @mutex: protect objects list */ + struct mutex mutex; + struct list_head objects; +}; + +struct lsdc_device { + struct drm_device base; + struct ttm_device bdev; + + /* @descp: features description of the DC variant */ + const struct lsdc_desc *descp; + struct pci_dev *dc; + struct pci_dev *gpu; + + struct loongson_gfxpll *gfxpll; + + /* @reglock: protects concurrent access */ + spinlock_t reglock; + + void __iomem *reg_base; + resource_size_t vram_base; + resource_size_t vram_size; + + resource_size_t gtt_base; + resource_size_t gtt_size; + + struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; + + struct lsdc_gem gem; + + u32 irq_status; + + /* tracking pinned memory */ + size_t vram_pinned_size; + size_t gtt_pinned_size; + + /* @num_output: count the number of active display pipe */ + unsigned int num_output; +}; + +static inline struct lsdc_device *tdev_to_ldev(struct ttm_device *bdev) +{ + return container_of(bdev, struct lsdc_device, bdev); +} + +static inline struct lsdc_device *to_lsdc(struct drm_device *ddev) +{ + return container_of(ddev, struct lsdc_device, base); +} + +static inline struct lsdc_crtc_state * +to_lsdc_crtc_state(struct drm_crtc_state *base) +{ + return container_of(base, struct lsdc_crtc_state, base); +} + +void lsdc_debugfs_init(struct drm_minor *minor); + +int ls7a1000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index, + bool no_vblank); + +int ls7a2000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index, + bool no_vblank); + +int lsdc_primary_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index); + +int ls7a1000_cursor_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index); + +int ls7a2000_cursor_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index); + +/* Registers access helpers */ + +static inline u32 lsdc_rreg32(struct lsdc_device *ldev, u32 offset) +{ + return readl(ldev->reg_base + offset); +} + +static inline void lsdc_wreg32(struct lsdc_device *ldev, u32 offset, u32 val) +{ + writel(val, ldev->reg_base + offset); +} + +static inline void lsdc_ureg32_set(struct lsdc_device *ldev, + u32 offset, + u32 mask) +{ + void __iomem *addr = ldev->reg_base + offset; + u32 val = readl(addr); + + writel(val | mask, addr); +} + +static inline void lsdc_ureg32_clr(struct lsdc_device *ldev, + u32 offset, + u32 mask) +{ + void __iomem *addr = ldev->reg_base + offset; + u32 val = readl(addr); + + writel(val & ~mask, addr); +} + +static inline u32 lsdc_pipe_rreg32(struct lsdc_device *ldev, + u32 offset, u32 pipe) +{ + return readl(ldev->reg_base + offset + pipe * CRTC_PIPE_OFFSET); +} + +static inline void lsdc_pipe_wreg32(struct lsdc_device *ldev, + u32 offset, u32 pipe, u32 val) +{ + writel(val, ldev->reg_base + offset + pipe * CRTC_PIPE_OFFSET); +} + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_gem.c b/drivers/gpu/drm/loongson/lsdc_gem.c new file mode 100644 index 0000000000..04293df2f0 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_gem.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/dma-buf.h> + +#include <drm/drm_debugfs.h> +#include <drm/drm_file.h> +#include <drm/drm_gem.h> +#include <drm/drm_prime.h> + +#include "lsdc_drv.h" +#include "lsdc_gem.h" +#include "lsdc_ttm.h" + +static int lsdc_gem_prime_pin(struct drm_gem_object *obj) +{ + struct lsdc_bo *lbo = gem_to_lsdc_bo(obj); + int ret; + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) + return ret; + + ret = lsdc_bo_pin(lbo, LSDC_GEM_DOMAIN_GTT, NULL); + if (likely(ret == 0)) + lbo->sharing_count++; + + lsdc_bo_unreserve(lbo); + + return ret; +} + +static void lsdc_gem_prime_unpin(struct drm_gem_object *obj) +{ + struct lsdc_bo *lbo = gem_to_lsdc_bo(obj); + int ret; + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) + return; + + lsdc_bo_unpin(lbo); + if (lbo->sharing_count) + lbo->sharing_count--; + + lsdc_bo_unreserve(lbo); +} + +static struct sg_table *lsdc_gem_prime_get_sg_table(struct drm_gem_object *obj) +{ + struct ttm_buffer_object *tbo = to_ttm_bo(obj); + struct ttm_tt *tt = tbo->ttm; + + if (!tt) { + drm_err(obj->dev, "sharing a buffer without backing memory\n"); + return ERR_PTR(-ENOMEM); + } + + return drm_prime_pages_to_sg(obj->dev, tt->pages, tt->num_pages); +} + +static void lsdc_gem_object_free(struct drm_gem_object *obj) +{ + struct ttm_buffer_object *tbo = to_ttm_bo(obj); + + if (tbo) + ttm_bo_put(tbo); +} + +static int lsdc_gem_object_vmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + struct ttm_buffer_object *tbo = to_ttm_bo(obj); + struct lsdc_bo *lbo = to_lsdc_bo(tbo); + int ret; + + if (lbo->vmap_count > 0) { + ++lbo->vmap_count; + goto out; + } + + ret = lsdc_bo_pin(lbo, 0, NULL); + if (unlikely(ret)) { + drm_err(obj->dev, "pin %p for vmap failed\n", lbo); + return ret; + } + + ret = ttm_bo_vmap(tbo, &lbo->map); + if (ret) { + drm_err(obj->dev, "ttm bo vmap failed\n"); + lsdc_bo_unpin(lbo); + return ret; + } + + lbo->vmap_count = 1; + +out: + *map = lbo->map; + + return 0; +} + +static void lsdc_gem_object_vunmap(struct drm_gem_object *obj, struct iosys_map *map) +{ + struct ttm_buffer_object *tbo = to_ttm_bo(obj); + struct lsdc_bo *lbo = to_lsdc_bo(tbo); + + if (unlikely(!lbo->vmap_count)) { + drm_warn(obj->dev, "%p is not mapped\n", lbo); + return; + } + + --lbo->vmap_count; + if (lbo->vmap_count == 0) { + ttm_bo_vunmap(tbo, &lbo->map); + + lsdc_bo_unpin(lbo); + } +} + +static int lsdc_gem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma) +{ + struct ttm_buffer_object *tbo = to_ttm_bo(obj); + int ret; + + ret = ttm_bo_mmap_obj(vma, tbo); + if (unlikely(ret)) { + drm_warn(obj->dev, "mmap %p failed\n", tbo); + return ret; + } + + drm_gem_object_put(obj); + + return 0; +} + +static const struct drm_gem_object_funcs lsdc_gem_object_funcs = { + .free = lsdc_gem_object_free, + .export = drm_gem_prime_export, + .pin = lsdc_gem_prime_pin, + .unpin = lsdc_gem_prime_unpin, + .get_sg_table = lsdc_gem_prime_get_sg_table, + .vmap = lsdc_gem_object_vmap, + .vunmap = lsdc_gem_object_vunmap, + .mmap = lsdc_gem_object_mmap, +}; + +struct drm_gem_object *lsdc_gem_object_create(struct drm_device *ddev, + u32 domain, + size_t size, + bool kerenl, + struct sg_table *sg, + struct dma_resv *resv) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_gem_object *gobj; + struct lsdc_bo *lbo; + int ret; + + lbo = lsdc_bo_create(ddev, domain, size, kerenl, sg, resv); + if (IS_ERR(lbo)) { + ret = PTR_ERR(lbo); + return ERR_PTR(ret); + } + + if (!sg) { + /* VRAM is filled with random data */ + lsdc_bo_clear(lbo); + } + + gobj = &lbo->tbo.base; + gobj->funcs = &lsdc_gem_object_funcs; + + /* tracking the BOs we created */ + mutex_lock(&ldev->gem.mutex); + list_add_tail(&lbo->list, &ldev->gem.objects); + mutex_unlock(&ldev->gem.mutex); + + return gobj; +} + +struct drm_gem_object * +lsdc_prime_import_sg_table(struct drm_device *ddev, + struct dma_buf_attachment *attach, + struct sg_table *sg) +{ + struct dma_resv *resv = attach->dmabuf->resv; + u64 size = attach->dmabuf->size; + struct drm_gem_object *gobj; + struct lsdc_bo *lbo; + + dma_resv_lock(resv, NULL); + gobj = lsdc_gem_object_create(ddev, LSDC_GEM_DOMAIN_GTT, size, false, + sg, resv); + dma_resv_unlock(resv); + + if (IS_ERR(gobj)) { + drm_err(ddev, "Failed to import sg table\n"); + return gobj; + } + + lbo = gem_to_lsdc_bo(gobj); + lbo->sharing_count = 1; + + return gobj; +} + +int lsdc_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_desc *descp = ldev->descp; + u32 domain = LSDC_GEM_DOMAIN_VRAM; + struct drm_gem_object *gobj; + size_t size; + u32 pitch; + u32 handle; + int ret; + + if (!args->width || !args->height) + return -EINVAL; + + if (args->bpp != 32 && args->bpp != 16) + return -EINVAL; + + pitch = args->width * args->bpp / 8; + pitch = ALIGN(pitch, descp->pitch_align); + size = pitch * args->height; + size = ALIGN(size, PAGE_SIZE); + + /* Maximum single bo size allowed is the half vram size available */ + if (size > ldev->vram_size / 2) { + drm_err(ddev, "Requesting(%zuMiB) failed\n", size >> 20); + return -ENOMEM; + } + + gobj = lsdc_gem_object_create(ddev, domain, size, false, NULL, NULL); + if (IS_ERR(gobj)) { + drm_err(ddev, "Failed to create gem object\n"); + return PTR_ERR(gobj); + } + + ret = drm_gem_handle_create(file, gobj, &handle); + + /* drop reference from allocate, handle holds it now */ + drm_gem_object_put(gobj); + if (ret) + return ret; + + args->pitch = pitch; + args->size = size; + args->handle = handle; + + return 0; +} + +int lsdc_dumb_map_offset(struct drm_file *filp, struct drm_device *ddev, + u32 handle, uint64_t *offset) +{ + struct drm_gem_object *gobj; + + gobj = drm_gem_object_lookup(filp, handle); + if (!gobj) + return -ENOENT; + + *offset = drm_vma_node_offset_addr(&gobj->vma_node); + + drm_gem_object_put(gobj); + + return 0; +} + +void lsdc_gem_init(struct drm_device *ddev) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + + mutex_init(&ldev->gem.mutex); + INIT_LIST_HEAD(&ldev->gem.objects); +} + +int lsdc_show_buffer_object(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); + struct lsdc_bo *lbo; + unsigned int i; + + mutex_lock(&ldev->gem.mutex); + + i = 0; + + list_for_each_entry(lbo, &ldev->gem.objects, list) { + struct ttm_buffer_object *tbo = &lbo->tbo; + struct ttm_resource *resource = tbo->resource; + + seq_printf(m, "bo[%04u][%p]: size: %8zuKiB %s offset: %8llx\n", + i, lbo, lsdc_bo_size(lbo) >> 10, + lsdc_mem_type_to_str(resource->mem_type), + lsdc_bo_gpu_offset(lbo)); + i++; + } + + mutex_unlock(&ldev->gem.mutex); + + seq_printf(m, "Pinned BO size: VRAM: %zuKiB, GTT: %zu KiB\n", + ldev->vram_pinned_size >> 10, ldev->gtt_pinned_size >> 10); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_gem.h b/drivers/gpu/drm/loongson/lsdc_gem.h new file mode 100644 index 0000000000..92cbb10e6e --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_gem.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_GEM_H__ +#define __LSDC_GEM_H__ + +#include <drm/drm_device.h> +#include <drm/drm_gem.h> + +struct drm_gem_object * +lsdc_prime_import_sg_table(struct drm_device *ddev, + struct dma_buf_attachment *attach, + struct sg_table *sg); + +int lsdc_dumb_map_offset(struct drm_file *file, + struct drm_device *dev, + u32 handle, + uint64_t *offset); + +int lsdc_dumb_create(struct drm_file *file, + struct drm_device *ddev, + struct drm_mode_create_dumb *args); + +void lsdc_gem_init(struct drm_device *ddev); +int lsdc_show_buffer_object(struct seq_file *m, void *arg); + +struct drm_gem_object * +lsdc_gem_object_create(struct drm_device *ddev, + u32 domain, + size_t size, + bool kerenl, + struct sg_table *sg, + struct dma_resv *resv); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_gfxpll.c b/drivers/gpu/drm/loongson/lsdc_gfxpll.c new file mode 100644 index 0000000000..249c09d703 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_gfxpll.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/delay.h> + +#include <drm/drm_file.h> +#include <drm/drm_managed.h> +#include <drm/drm_print.h> + +#include "lsdc_drv.h" + +/* + * GFX PLL is the PLL used by DC, GMC and GPU, the structure of the GFX PLL + * may suffer from change across chip variants. + * + * + * +-------------+ sel_out_dc + * +----| / div_out_0 | _____/ _____ DC + * | +-------------+ + * refclk +---------+ +-------+ | +-------------+ sel_out_gmc + * ---+---> | div_ref | ---> | loopc | --+--> | / div_out_1 | _____/ _____ GMC + * | +---------+ +-------+ | +-------------+ + * | / * | +-------------+ sel_out_gpu + * | +----| / div_out_2 | _____/ _____ GPU + * | +-------------+ + * | ^ + * | | + * +--------------------------- bypass ----------------------+ + */ + +struct loongson_gfxpll_bitmap { + /* Byte 0 ~ Byte 3 */ + unsigned div_out_dc : 7; /* 6 : 0 DC output clock divider */ + unsigned div_out_gmc : 7; /* 13 : 7 GMC output clock divider */ + unsigned div_out_gpu : 7; /* 20 : 14 GPU output clock divider */ + unsigned loopc : 9; /* 29 : 21 clock multiplier */ + unsigned _reserved_1_ : 2; /* 31 : 30 */ + + /* Byte 4 ~ Byte 7 */ + unsigned div_ref : 7; /* 38 : 32 Input clock divider */ + unsigned locked : 1; /* 39 PLL locked indicator */ + unsigned sel_out_dc : 1; /* 40 dc output clk enable */ + unsigned sel_out_gmc : 1; /* 41 gmc output clk enable */ + unsigned sel_out_gpu : 1; /* 42 gpu output clk enable */ + unsigned set_param : 1; /* 43 Trigger the update */ + unsigned bypass : 1; /* 44 */ + unsigned powerdown : 1; /* 45 */ + unsigned _reserved_2_ : 18; /* 46 : 63 no use */ +}; + +union loongson_gfxpll_reg_bitmap { + struct loongson_gfxpll_bitmap bitmap; + u32 w[2]; + u64 d; +}; + +static void __gfxpll_rreg(struct loongson_gfxpll *this, + union loongson_gfxpll_reg_bitmap *reg) +{ +#if defined(CONFIG_64BIT) + reg->d = readq(this->mmio); +#else + reg->w[0] = readl(this->mmio); + reg->w[1] = readl(this->mmio + 4); +#endif +} + +/* Update new parameters to the hardware */ + +static int loongson_gfxpll_update(struct loongson_gfxpll * const this, + struct loongson_gfxpll_parms const *pin) +{ + /* None, TODO */ + + return 0; +} + +static void loongson_gfxpll_get_rates(struct loongson_gfxpll * const this, + unsigned int *dc, + unsigned int *gmc, + unsigned int *gpu) +{ + struct loongson_gfxpll_parms *pparms = &this->parms; + union loongson_gfxpll_reg_bitmap gfxpll_reg; + unsigned int pre_output; + unsigned int dc_mhz; + unsigned int gmc_mhz; + unsigned int gpu_mhz; + + __gfxpll_rreg(this, &gfxpll_reg); + + pparms->div_ref = gfxpll_reg.bitmap.div_ref; + pparms->loopc = gfxpll_reg.bitmap.loopc; + + pparms->div_out_dc = gfxpll_reg.bitmap.div_out_dc; + pparms->div_out_gmc = gfxpll_reg.bitmap.div_out_gmc; + pparms->div_out_gpu = gfxpll_reg.bitmap.div_out_gpu; + + pre_output = pparms->ref_clock / pparms->div_ref * pparms->loopc; + + dc_mhz = pre_output / pparms->div_out_dc / 1000; + gmc_mhz = pre_output / pparms->div_out_gmc / 1000; + gpu_mhz = pre_output / pparms->div_out_gpu / 1000; + + if (dc) + *dc = dc_mhz; + + if (gmc) + *gmc = gmc_mhz; + + if (gpu) + *gpu = gpu_mhz; +} + +static void loongson_gfxpll_print(struct loongson_gfxpll * const this, + struct drm_printer *p, + bool verbose) +{ + struct loongson_gfxpll_parms *parms = &this->parms; + unsigned int dc, gmc, gpu; + + if (verbose) { + drm_printf(p, "reference clock: %u\n", parms->ref_clock); + drm_printf(p, "div_ref = %u\n", parms->div_ref); + drm_printf(p, "loopc = %u\n", parms->loopc); + + drm_printf(p, "div_out_dc = %u\n", parms->div_out_dc); + drm_printf(p, "div_out_gmc = %u\n", parms->div_out_gmc); + drm_printf(p, "div_out_gpu = %u\n", parms->div_out_gpu); + } + + this->funcs->get_rates(this, &dc, &gmc, &gpu); + + drm_printf(p, "dc: %uMHz, gmc: %uMHz, gpu: %uMHz\n", dc, gmc, gpu); +} + +/* GFX (DC, GPU, GMC) PLL initialization and destroy function */ + +static void loongson_gfxpll_fini(struct drm_device *ddev, void *data) +{ + struct loongson_gfxpll *this = (struct loongson_gfxpll *)data; + + iounmap(this->mmio); + + kfree(this); +} + +static int loongson_gfxpll_init(struct loongson_gfxpll * const this) +{ + struct loongson_gfxpll_parms *pparms = &this->parms; + struct drm_printer printer = drm_info_printer(this->ddev->dev); + + pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ; + + this->mmio = ioremap(this->reg_base, this->reg_size); + if (IS_ERR_OR_NULL(this->mmio)) + return -ENOMEM; + + this->funcs->print(this, &printer, false); + + return 0; +} + +static const struct loongson_gfxpll_funcs lsdc_gmc_gpu_funcs = { + .init = loongson_gfxpll_init, + .update = loongson_gfxpll_update, + .get_rates = loongson_gfxpll_get_rates, + .print = loongson_gfxpll_print, +}; + +int loongson_gfxpll_create(struct drm_device *ddev, + struct loongson_gfxpll **ppout) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp); + struct loongson_gfxpll *this; + int ret; + + this = kzalloc(sizeof(*this), GFP_KERNEL); + if (IS_ERR_OR_NULL(this)) + return -ENOMEM; + + this->ddev = ddev; + this->reg_size = gfx->gfxpll.reg_size; + this->reg_base = gfx->conf_reg_base + gfx->gfxpll.reg_offset; + this->funcs = &lsdc_gmc_gpu_funcs; + + ret = this->funcs->init(this); + if (unlikely(ret)) { + kfree(this); + return ret; + } + + *ppout = this; + + return drmm_add_action_or_reset(ddev, loongson_gfxpll_fini, this); +} diff --git a/drivers/gpu/drm/loongson/lsdc_gfxpll.h b/drivers/gpu/drm/loongson/lsdc_gfxpll.h new file mode 100644 index 0000000000..9d59cbfc14 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_gfxpll.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_GFXPLL_H__ +#define __LSDC_GFXPLL_H__ + +#include <drm/drm_device.h> + +struct loongson_gfxpll; + +struct loongson_gfxpll_parms { + unsigned int ref_clock; + unsigned int div_ref; + unsigned int loopc; + unsigned int div_out_dc; + unsigned int div_out_gmc; + unsigned int div_out_gpu; +}; + +struct loongson_gfxpll_funcs { + int (*init)(struct loongson_gfxpll * const this); + + int (*update)(struct loongson_gfxpll * const this, + struct loongson_gfxpll_parms const *pin); + + void (*get_rates)(struct loongson_gfxpll * const this, + unsigned int *dc, unsigned int *gmc, unsigned int *gpu); + + void (*print)(struct loongson_gfxpll * const this, + struct drm_printer *printer, bool verbose); +}; + +struct loongson_gfxpll { + struct drm_device *ddev; + void __iomem *mmio; + + /* PLL register offset */ + u32 reg_base; + /* PLL register size in bytes */ + u32 reg_size; + + const struct loongson_gfxpll_funcs *funcs; + + struct loongson_gfxpll_parms parms; +}; + +int loongson_gfxpll_create(struct drm_device *ddev, + struct loongson_gfxpll **ppout); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_i2c.c b/drivers/gpu/drm/loongson/lsdc_i2c.c new file mode 100644 index 0000000000..9625d0b1d0 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_i2c.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <drm/drm_managed.h> + +#include "lsdc_drv.h" +#include "lsdc_output.h" + +/* + * __lsdc_gpio_i2c_set - set the state of a gpio pin indicated by mask + * @mask: gpio pin mask + * @state: "0" for low, "1" for high + */ +static void __lsdc_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state) +{ + struct lsdc_device *ldev = to_lsdc(li2c->ddev); + unsigned long flags; + u8 val; + + spin_lock_irqsave(&ldev->reglock, flags); + + if (state) { + /* + * Setting this pin as input directly, write 1 for input. + * The external pull-up resistor will pull the level up + */ + val = readb(li2c->dir_reg); + val |= mask; + writeb(val, li2c->dir_reg); + } else { + /* First set this pin as output, write 0 for output */ + val = readb(li2c->dir_reg); + val &= ~mask; + writeb(val, li2c->dir_reg); + + /* Then, make this pin output 0 */ + val = readb(li2c->dat_reg); + val &= ~mask; + writeb(val, li2c->dat_reg); + } + + spin_unlock_irqrestore(&ldev->reglock, flags); +} + +/* + * __lsdc_gpio_i2c_get - read value back from the gpio pin indicated by mask + * @mask: gpio pin mask + * return "0" for low, "1" for high + */ +static int __lsdc_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) +{ + struct lsdc_device *ldev = to_lsdc(li2c->ddev); + unsigned long flags; + u8 val; + + spin_lock_irqsave(&ldev->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(&ldev->reglock, flags); + + return (val & mask) ? 1 : 0; +} + +static void lsdc_gpio_i2c_set_sda(void *i2c, int state) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* set state on the li2c->sda pin */ + return __lsdc_gpio_i2c_set(li2c, li2c->sda, state); +} + +static void lsdc_gpio_i2c_set_scl(void *i2c, int state) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* set state on the li2c->scl pin */ + return __lsdc_gpio_i2c_set(li2c, li2c->scl, state); +} + +static int lsdc_gpio_i2c_get_sda(void *i2c) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* read value from the li2c->sda pin */ + return __lsdc_gpio_i2c_get(li2c, li2c->sda); +} + +static int lsdc_gpio_i2c_get_scl(void *i2c) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* read the value from the li2c->scl pin */ + return __lsdc_gpio_i2c_get(li2c, li2c->scl); +} + +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) +{ + struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; + + if (li2c) { + i2c_del_adapter(&li2c->adapter); + kfree(li2c); + } +} + +/* + * The DC in ls7a1000/ls7a2000/ls2k2000 has builtin gpio hardware + * + * @reg_base: gpio reg base + * @index: output channel index, 0 for PIPE0, 1 for PIPE1 + */ +int lsdc_create_i2c_chan(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + unsigned int index) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + struct i2c_adapter *adapter; + struct lsdc_i2c *li2c; + int ret; + + li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); + if (!li2c) + return -ENOMEM; + + dispipe->li2c = li2c; + + if (index == 0) { + li2c->sda = 0x01; /* pin 0 */ + li2c->scl = 0x02; /* pin 1 */ + } else if (index == 1) { + li2c->sda = 0x04; /* pin 2 */ + li2c->scl = 0x08; /* pin 3 */ + } else { + return -ENOENT; + } + + li2c->ddev = ddev; + li2c->dir_reg = ldev->reg_base + LS7A_DC_GPIO_DIR_REG; + li2c->dat_reg = ldev->reg_base + LS7A_DC_GPIO_DAT_REG; + + li2c->bit.setsda = lsdc_gpio_i2c_set_sda; + li2c->bit.setscl = lsdc_gpio_i2c_set_scl; + li2c->bit.getsda = lsdc_gpio_i2c_get_sda; + li2c->bit.getscl = lsdc_gpio_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), "lsdc-i2c%u", index); + + i2c_set_adapdata(adapter, li2c); + + ret = i2c_bit_add_bus(adapter); + if (ret) { + kfree(li2c); + return ret; + } + + ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); + if (ret) + return ret; + + drm_info(ddev, "%s(sda pin mask=%u, scl pin mask=%u) created\n", + adapter->name, li2c->sda, li2c->scl); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_i2c.h b/drivers/gpu/drm/loongson/lsdc_i2c.h new file mode 100644 index 0000000000..88cd1a1817 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_i2c.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_I2C_H__ +#define __LSDC_I2C_H__ + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +struct lsdc_i2c { + struct i2c_adapter adapter; + struct i2c_algo_bit_data bit; + struct drm_device *ddev; + void __iomem *dir_reg; + void __iomem *dat_reg; + /* pin bit mask */ + u8 sda; + u8 scl; +}; + +struct lsdc_display_pipe; + +int lsdc_create_i2c_chan(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + unsigned int index); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_irq.c b/drivers/gpu/drm/loongson/lsdc_irq.c new file mode 100644 index 0000000000..efdc4d1079 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_irq.c @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <drm/drm_vblank.h> + +#include "lsdc_irq.h" + +/* + * For the DC in LS7A2000, clearing interrupt status is achieved by + * write "1" to LSDC_INT_REG. + * + * For the DC in LS7A1000, clear interrupt status is achieved by write "0" + * to LSDC_INT_REG. + * + * Two different hardware engineers modify it as their will. + */ + +irqreturn_t ls7a2000_dc_irq_handler(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + /* Read the interrupt status */ + val = lsdc_rreg32(ldev, LSDC_INT_REG); + if ((val & INT_STATUS_MASK) == 0) { + drm_warn(ddev, "no interrupt occurs\n"); + return IRQ_NONE; + } + + ldev->irq_status = val; + + /* write "1" to clear the interrupt status */ + lsdc_wreg32(ldev, LSDC_INT_REG, val); + + if (ldev->irq_status & INT_CRTC0_VSYNC) + drm_handle_vblank(ddev, 0); + + if (ldev->irq_status & INT_CRTC1_VSYNC) + drm_handle_vblank(ddev, 1); + + return IRQ_HANDLED; +} + +/* For the DC in LS7A1000 and LS2K1000 */ +irqreturn_t ls7a1000_dc_irq_handler(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + /* Read the interrupt status */ + val = lsdc_rreg32(ldev, LSDC_INT_REG); + if ((val & INT_STATUS_MASK) == 0) { + drm_warn(ddev, "no interrupt occurs\n"); + return IRQ_NONE; + } + + ldev->irq_status = val; + + /* write "0" to clear the interrupt status */ + val &= ~(INT_CRTC0_VSYNC | INT_CRTC1_VSYNC); + lsdc_wreg32(ldev, LSDC_INT_REG, val); + + if (ldev->irq_status & INT_CRTC0_VSYNC) + drm_handle_vblank(ddev, 0); + + if (ldev->irq_status & INT_CRTC1_VSYNC) + drm_handle_vblank(ddev, 1); + + return IRQ_HANDLED; +} diff --git a/drivers/gpu/drm/loongson/lsdc_irq.h b/drivers/gpu/drm/loongson/lsdc_irq.h new file mode 100644 index 0000000000..726cb3018b --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_irq.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_IRQ_H__ +#define __LSDC_IRQ_H__ + +#include <linux/irqreturn.h> + +#include "lsdc_drv.h" + +irqreturn_t ls7a1000_dc_irq_handler(int irq, void *arg); +irqreturn_t ls7a2000_dc_irq_handler(int irq, void *arg); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_output.h b/drivers/gpu/drm/loongson/lsdc_output.h new file mode 100644 index 0000000000..097789051a --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_output.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_OUTPUT_H__ +#define __LSDC_OUTPUT_H__ + +#include "lsdc_drv.h" + +int ls7a1000_output_init(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int index); + +int ls7a2000_output_init(struct drm_device *ldev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int index); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a1000.c b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c new file mode 100644 index 0000000000..6fc8dd1c7d --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_output_7a1000.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_probe_helper.h> + +#include "lsdc_drv.h" +#include "lsdc_output.h" + +/* + * The display controller in the LS7A1000 exports two DVO interfaces, thus + * external encoder is required, except connected to the DPI panel directly. + * + * ___________________ _________ + * | -------| | | + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | + * | _ _ -------| ^ ^ |_________| + * | | | | | +------+ | | | + * | |_| |_| | i2c6 | <--------+-------------+ + * | +------+ | + * | | + * | DC in LS7A1000 | + * | | + * | _ _ +------+ | + * | | | | | | i2c7 | <--------+-------------+ + * | |_| |_| +------+ | | | _________ + * | -------| | | | | + * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | + * | -------| |_________| + * |___________________| + * + * Currently, we assume the external encoders connected to the DVO are + * transparent. Loongson's DVO interface can directly drive RGB888 panels. + * + * TODO: Add support for non-transparent encoders + */ + +static int ls7a1000_dpi_connector_get_modes(struct drm_connector *conn) +{ + unsigned int num = 0; + struct edid *edid; + + if (conn->ddc) { + edid = drm_get_edid(conn, conn->ddc); + if (edid) { + drm_connector_update_edid_property(conn, edid); + num = drm_add_edid_modes(conn, edid); + kfree(edid); + } + + return num; + } + + num = drm_add_modes_noedid(conn, 1920, 1200); + + drm_set_preferred_mode(conn, 1024, 768); + + return num; +} + +static struct drm_encoder * +ls7a1000_dpi_connector_get_best_encoder(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct lsdc_output *output = connector_to_lsdc_output(connector); + + return &output->encoder; +} + +static const struct drm_connector_helper_funcs +ls7a1000_dpi_connector_helpers = { + .atomic_best_encoder = ls7a1000_dpi_connector_get_best_encoder, + .get_modes = ls7a1000_dpi_connector_get_modes, +}; + +static enum drm_connector_status +ls7a1000_dpi_connector_detect(struct drm_connector *connector, bool force) +{ + struct i2c_adapter *ddc = connector->ddc; + + if (ddc) { + if (drm_probe_ddc(ddc)) + return connector_status_connected; + + return connector_status_disconnected; + } + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ls7a1000_dpi_connector_funcs = { + .detect = ls7a1000_dpi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .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 void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + /* + * We need this for S3 support, screen will not lightup if don't set + * this register correctly. + */ + lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, + PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); +} + +static void ls7a1000_pipe1_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + /* + * We need this for S3 support, screen will not lightup if don't set + * this register correctly. + */ + + /* DVO */ + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, + BIT(31) | PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); +} + +static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = { + { + .reset = ls7a1000_pipe0_encoder_reset, + .destroy = drm_encoder_cleanup, + }, + { + .reset = ls7a1000_pipe1_encoder_reset, + .destroy = drm_encoder_cleanup, + }, +}; + +int ls7a1000_output_init(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int index) +{ + struct lsdc_output *output = &dispipe->output; + struct drm_encoder *encoder = &output->encoder; + struct drm_connector *connector = &output->connector; + int ret; + + ret = drm_encoder_init(ddev, encoder, &ls7a1000_encoder_funcs[index], + DRM_MODE_ENCODER_TMDS, "encoder-%u", index); + if (ret) + return ret; + + encoder->possible_crtcs = BIT(index); + + ret = drm_connector_init_with_ddc(ddev, connector, + &ls7a1000_dpi_connector_funcs, + DRM_MODE_CONNECTOR_DPI, ddc); + if (ret) + return ret; + + drm_info(ddev, "display pipe-%u has a DVO\n", index); + + drm_connector_helper_add(connector, &ls7a1000_dpi_connector_helpers); + + drm_connector_attach_encoder(connector, encoder); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_output_7a2000.c b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c new file mode 100644 index 0000000000..ce3dabec88 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_output_7a2000.c @@ -0,0 +1,552 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/delay.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_debugfs.h> +#include <drm/drm_edid.h> +#include <drm/drm_probe_helper.h> + +#include "lsdc_drv.h" +#include "lsdc_output.h" + +/* + * The display controller in LS7A2000 has two display pipes + * Display pipe 0 is attached with a built-in transparent VGA encoder and + * a built-in HDMI encoder. + * Display pipe 1 has only one built-in HDMI encoder connected. + * ______________________ _____________ + * | +-----+ | | | + * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA Monitor |<---+ + * | | +-----+ | |_____________| | + * | | | ______________ | + * | | +------+ | | | | + * | +--> | HDMI | ----> HDMI Connector --> | HDMI Monitor |<--+ + * | +------+ | |______________| | + * | +------+ | | + * | | i2c6 | <-------------------------------------------+ + * | +------+ | + * | | + * | DC in LS7A2000 | + * | | + * | +------+ | + * | | i2c7 | <--------------------------------+ + * | +------+ | | + * | | ______|_______ + * | +------+ | | | + * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI Monitor | + * | +------+ | |______________| + * |______________________| + */ + +static int ls7a2000_connector_get_modes(struct drm_connector *connector) +{ + unsigned int num = 0; + struct edid *edid; + + if (connector->ddc) { + edid = drm_get_edid(connector, connector->ddc); + if (edid) { + drm_connector_update_edid_property(connector, edid); + num = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + return num; + } + + num = drm_add_modes_noedid(connector, 1920, 1200); + + drm_set_preferred_mode(connector, 1024, 768); + + return num; +} + +static struct drm_encoder * +ls7a2000_connector_get_best_encoder(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct lsdc_output *output = connector_to_lsdc_output(connector); + + return &output->encoder; +} + +static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = { + .atomic_best_encoder = ls7a2000_connector_get_best_encoder, + .get_modes = ls7a2000_connector_get_modes, +}; + +/* debugfs */ + +#define LSDC_HDMI_REG(i, reg) { \ + .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ + .offset = LSDC_HDMI##i##_##reg##_REG, \ +} + +static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = { + LSDC_HDMI_REG(0, ZONE), + LSDC_HDMI_REG(0, INTF_CTRL), + LSDC_HDMI_REG(0, PHY_CTRL), + LSDC_HDMI_REG(0, PHY_PLL), + LSDC_HDMI_REG(0, AVI_INFO_CRTL), + LSDC_HDMI_REG(0, PHY_CAL), + LSDC_HDMI_REG(0, AUDIO_PLL_LO), + LSDC_HDMI_REG(0, AUDIO_PLL_HI), + {NULL, 0}, /* MUST be {NULL, 0} terminated */ +}; + +static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = { + LSDC_HDMI_REG(1, ZONE), + LSDC_HDMI_REG(1, INTF_CTRL), + LSDC_HDMI_REG(1, PHY_CTRL), + LSDC_HDMI_REG(1, PHY_PLL), + LSDC_HDMI_REG(1, AVI_INFO_CRTL), + LSDC_HDMI_REG(1, PHY_CAL), + LSDC_HDMI_REG(1, AUDIO_PLL_LO), + LSDC_HDMI_REG(1, AUDIO_PLL_HI), + {NULL, 0}, /* MUST be {NULL, 0} terminated */ +}; + +static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data) +{ + 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); + const struct lsdc_reg32 *preg; + + preg = (const struct lsdc_reg32 *)node->info_ent->data; + + while (preg->name) { + u32 offset = preg->offset; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + preg->name, offset, lsdc_rreg32(ldev, offset)); + ++preg; + } + + return 0; +} + +static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = { + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs }, +}; + +static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = { + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs }, +}; + +static void ls7a2000_hdmi0_late_register(struct drm_connector *connector, + struct dentry *root) +{ + struct drm_device *ddev = connector->dev; + struct drm_minor *minor = ddev->primary; + + drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files, + ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files), + root, minor); +} + +static void ls7a2000_hdmi1_late_register(struct drm_connector *connector, + struct dentry *root) +{ + struct drm_device *ddev = connector->dev; + struct drm_minor *minor = ddev->primary; + + drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files, + ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files), + root, minor); +} + +/* monitor present detection */ + +static enum drm_connector_status +ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *ddev = connector->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (val & HDMI0_HPD_FLAG) + return connector_status_connected; + + if (connector->ddc) { + if (drm_probe_ddc(connector->ddc)) + return connector_status_connected; + + return connector_status_disconnected; + } + + return connector_status_unknown; +} + +static enum drm_connector_status +ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force) +{ + struct lsdc_device *ldev = to_lsdc(connector->dev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (val & HDMI1_HPD_FLAG) + return connector_status_connected; + + return connector_status_disconnected; +} + +static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = { + { + .detect = ls7a2000_hdmi0_vga_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .debugfs_init = ls7a2000_hdmi0_late_register, + }, + { + .detect = ls7a2000_hdmi1_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .debugfs_init = ls7a2000_hdmi1_late_register, + }, +}; + +/* Even though some board has only one hdmi on display pipe 1, + * We still need hook lsdc_encoder_funcs up on display pipe 0, + * This is because we need its reset() callback get called, to + * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c. + * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly. + */ +static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; + lsdc_wreg32(ldev, LSDC_CRTC0_DVO_CONF_REG, val); + + /* using software gpio emulated i2c */ + val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); + val &= ~HW_I2C_EN; + lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); + + /* help the hdmi phy to get out of reset state */ + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, HDMI_PHY_RESET_N); + + mdelay(20); + + drm_dbg(ddev, "HDMI-0 Reset\n"); +} + +static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; + lsdc_wreg32(ldev, LSDC_CRTC1_DVO_CONF_REG, val); + + /* using software gpio emulated i2c */ + val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); + val &= ~HW_I2C_EN; + lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); + + /* help the hdmi phy to get out of reset state */ + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, HDMI_PHY_RESET_N); + + mdelay(20); + + drm_dbg(ddev, "HDMI-1 Reset\n"); +} + +static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = { + { + .reset = ls7a2000_hdmi0_encoder_reset, + .destroy = drm_encoder_cleanup, + }, + { + .reset = ls7a2000_hdmi1_encoder_reset, + .destroy = drm_encoder_cleanup, + }, +}; + +static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct lsdc_output *output = encoder_to_lsdc_output(encoder); + struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); + unsigned int index = dispipe->index; + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct hdmi_avi_infoframe infoframe; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE]; + unsigned int content0, content1, content2, content3; + int err; + + err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, + &output->connector, + mode); + if (err < 0) { + drm_err(ddev, "failed to setup AVI infoframe: %d\n", err); + return err; + } + + /* Fixed infoframe configuration not linked to the mode */ + infoframe.colorspace = HDMI_COLORSPACE_RGB; + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + infoframe.colorimetry = HDMI_COLORIMETRY_NONE; + + err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); + if (err < 0) { + drm_err(ddev, "failed to pack AVI infoframe: %d\n", err); + return err; + } + + content0 = *(unsigned int *)ptr; + content1 = *(ptr + 4); + content2 = *(unsigned int *)(ptr + 5); + content3 = *(unsigned int *)(ptr + 9); + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0); + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1); + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2); + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3); + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index, + AVI_PKT_ENABLE | AVI_PKT_UPDATE); + + drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index); + + return 0; +} + +static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct lsdc_output *output = encoder_to_lsdc_output(encoder); + struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); + unsigned int index = dispipe->index; + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + /* Disable the hdmi phy */ + val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index); + val &= ~HDMI_PHY_EN; + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); + + /* Disable the hdmi interface */ + val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index); + val &= ~HDMI_INTERFACE_EN; + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); + + drm_dbg(ddev, "HDMI-%u disabled\n", index); +} + +static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_output *output = encoder_to_lsdc_output(encoder); + struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); + unsigned int index = dispipe->index; + u32 val; + + /* datasheet say it should larger than 48 */ + val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT; + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val); + + val = HDMI_PHY_TERM_STATUS | + HDMI_PHY_TERM_DET_EN | + HDMI_PHY_TERM_H_EN | + HDMI_PHY_TERM_L_EN | + HDMI_PHY_RESET_N | + HDMI_PHY_EN; + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); + + udelay(2); + + val = HDMI_CTL_PERIOD_MODE | + HDMI_AUDIO_EN | + HDMI_PACKET_EN | + HDMI_INTERFACE_EN | + (8 << HDMI_VIDEO_PREAMBLE_SHIFT); + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); + + drm_dbg(ddev, "HDMI-%u enabled\n", index); +} + +/* + * Fout = M * Fin + * + * M = (4 * LF) / (IDF * ODF) + * + * IDF: Input Division Factor + * ODF: Output Division Factor + * LF: Loop Factor + * M: Required Mult + * + * +--------------------------------------------------------+ + * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) | + * |-------------------+----+-----+----+-----+--------------| + * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 | + * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 | + * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 | + * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 | + * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 | + * +--------------------------------------------------------+ + */ +static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev, + int fin, + unsigned int index) +{ + struct drm_device *ddev = &ldev->base; + int count = 0; + u32 val; + + /* Firstly, disable phy pll */ + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0); + + /* + * Most of time, loongson HDMI require M = 10 + * for example, 10 = (4 * 40) / (8 * 2) + * here, write "1" to the ODF will get "2" + */ + + if (fin >= 170000) + val = (16 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (0 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 85000) + val = (8 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (1 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 42500) + val = (4 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (2 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 21250) + val = (2 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (3 << HDMI_PLL_ODF_SHIFT); + else + val = (1 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (4 << HDMI_PLL_ODF_SHIFT); + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); + + val |= HDMI_PLL_ENABLE; + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); + + udelay(2); + + drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin); + + /* Wait hdmi phy pll lock */ + do { + val = lsdc_pipe_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index); + + if (val & HDMI_PLL_LOCKED) { + drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n", + index, count); + break; + } + ++count; + } while (count < 1000); + + lsdc_pipe_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0); + + if (count >= 1000) + drm_err(ddev, "Setting HDMI-%u PLL failed\n", index); +} + +static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct lsdc_output *output = encoder_to_lsdc_output(encoder); + struct lsdc_display_pipe *dispipe = output_to_display_pipe(output); + unsigned int index = dispipe->index; + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_display_mode *mode = &crtc_state->mode; + + ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, index); + + ls7a2000_hdmi_set_avi_infoframe(encoder, mode); + + drm_dbg(ddev, "%s modeset finished\n", encoder->name); +} + +static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = { + .atomic_disable = ls7a2000_hdmi_atomic_disable, + .atomic_enable = ls7a2000_hdmi_atomic_enable, + .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set, +}; + +/* + * For LS7A2000: + * + * 1) Most of board export one vga + hdmi output interface. + * 2) Yet, Some boards export double hdmi output interface. + * 3) Still have boards export three output(2 hdmi + 1 vga). + * + * So let's hook hdmi helper funcs to all display pipe, don't miss. + * writing hdmi register do no harms. + */ +int ls7a2000_output_init(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int pipe) +{ + struct lsdc_output *output = &dispipe->output; + struct drm_encoder *encoder = &output->encoder; + struct drm_connector *connector = &output->connector; + int ret; + + ret = drm_encoder_init(ddev, encoder, &ls7a2000_encoder_funcs[pipe], + DRM_MODE_ENCODER_TMDS, "encoder-%u", pipe); + if (ret) + return ret; + + encoder->possible_crtcs = BIT(pipe); + + drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs); + + ret = drm_connector_init_with_ddc(ddev, connector, + &ls7a2000_hdmi_connector_funcs[pipe], + DRM_MODE_CONNECTOR_HDMIA, ddc); + if (ret) + return ret; + + drm_info(ddev, "display pipe-%u has HDMI %s\n", pipe, pipe ? "" : "and/or VGA"); + + drm_connector_helper_add(connector, &ls7a2000_connector_helpers); + + drm_connector_attach_encoder(connector, encoder); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_pixpll.c b/drivers/gpu/drm/loongson/lsdc_pixpll.c new file mode 100644 index 0000000000..2609a2256d --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_pixpll.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <linux/delay.h> + +#include <drm/drm_managed.h> + +#include "lsdc_drv.h" + +/* + * The structure of the pixel PLL registers is evolved with times, + * it can be different across different chip also. + */ + +/* size is u64, note that all loongson's cpu is little endian. + * This structure is same for ls7a2000, ls7a1000 and ls2k2000. + */ +struct lsdc_pixpll_reg { + /* Byte 0 ~ Byte 3 */ + unsigned div_out : 7; /* 6 : 0 Output clock divider */ + unsigned _reserved_1_ : 14; /* 20 : 7 */ + unsigned loopc : 9; /* 29 : 21 Clock multiplier */ + unsigned _reserved_2_ : 2; /* 31 : 30 */ + + /* Byte 4 ~ Byte 7 */ + unsigned div_ref : 7; /* 38 : 32 Input clock divider */ + unsigned locked : 1; /* 39 PLL locked indicator */ + unsigned sel_out : 1; /* 40 output clk selector */ + unsigned _reserved_3_ : 2; /* 42 : 41 */ + unsigned set_param : 1; /* 43 Trigger the update */ + unsigned bypass : 1; /* 44 */ + unsigned powerdown : 1; /* 45 */ + unsigned _reserved_4_ : 18; /* 46 : 63 no use */ +}; + +union lsdc_pixpll_reg_bitmap { + struct lsdc_pixpll_reg bitmap; + u32 w[2]; + u64 d; +}; + +struct clk_to_pixpll_parms_lookup_t { + unsigned int clock; /* kHz */ + + 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; +}; + +static const struct clk_to_pixpll_parms_lookup_t pixpll_parms_table[] = { + {148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */ + {141750, 1920, 1080, 60, 11, 78, 5}, /* 1920x1080@60Hz */ + /* 1920x1080@50Hz */ + {174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */ + {181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */ + {297000, 2560, 1080, 30, 8, 95, 4}, /* 3840x2160@30Hz */ + {301992, 1920, 1080, 100, 10, 151, 5}, /* 1920x1080@100Hz */ + {146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */ + {135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */ + {119000, 1680, 1050, 60, 20, 119, 5}, /* 1680x1050@60Hz */ + {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 */ +}; + +static void lsdc_pixel_pll_free(struct drm_device *ddev, void *data) +{ + struct lsdc_pixpll *this = (struct lsdc_pixpll *)data; + + iounmap(this->mmio); + + kfree(this->priv); + + drm_dbg(ddev, "pixpll private data freed\n"); +} + +/* + * ioremap the device dependent PLL registers + * + * @this: point to the object where this function is called from + */ +static int lsdc_pixel_pll_setup(struct lsdc_pixpll * const this) +{ + struct lsdc_pixpll_parms *pparms; + + this->mmio = ioremap(this->reg_base, this->reg_size); + if (!this->mmio) + return -ENOMEM; + + pparms = kzalloc(sizeof(*pparms), GFP_KERNEL); + if (!pparms) { + iounmap(this->mmio); + return -ENOMEM; + } + + pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ; + + this->priv = pparms; + + return drmm_add_action_or_reset(this->ddev, lsdc_pixel_pll_free, this); +} + +/* + * Find a set of pll parameters from a static local table which avoid + * computing the pll parameter eachtime a modeset is triggered. + * + * @this: point to the object where 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 0 if success, return -1 if not found. + */ +static int lsdc_pixpll_find(struct lsdc_pixpll * const this, + unsigned int clock, + struct lsdc_pixpll_parms *pout) +{ + unsigned int num = ARRAY_SIZE(pixpll_parms_table); + const struct clk_to_pixpll_parms_lookup_t *pt; + unsigned int i; + + for (i = 0; i < num; ++i) { + pt = &pixpll_parms_table[i]; + + if (clock == pt->clock) { + pout->div_ref = pt->div_ref; + pout->loopc = pt->loopc; + pout->div_out = pt->div_out; + + return 0; + } + } + + drm_dbg_kms(this->ddev, "pixel clock %u: miss\n", clock); + + return -1; +} + +/* + * 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 determined by the oscillator mounted on motherboard(100MHz + * in almost all board) + * + * @this: point to the object from where this function is called + * @clock: the desired output pixel clock, the unit is kHz + * @pout: point to the out struct of lsdc_pixpll_parms + * + * Return 0 if a set of parameter is found, otherwise return the error + * between clock_kHz we wanted and the most closest candidate with it. + */ +static int lsdc_pixel_pll_compute(struct lsdc_pixpll * const this, + unsigned int clock, + struct lsdc_pixpll_parms *pout) +{ + struct lsdc_pixpll_parms *pparms = this->priv; + unsigned int refclk = pparms->ref_clock; + const unsigned int tolerance = 1000; + unsigned int min = tolerance; + unsigned int div_out, loopc, div_ref; + unsigned int computed; + + if (!lsdc_pixpll_find(this, clock, pout)) + return 0; + + for (div_out = 6; div_out < 64; div_out++) { + for (div_ref = 3; div_ref < 6; div_ref++) { + for (loopc = 6; loopc < 161; loopc++) { + unsigned int diff = 0; + + if (loopc < 12 * div_ref) + continue; + if (loopc > 32 * div_ref) + continue; + + computed = refclk / div_ref * loopc / div_out; + + if (clock >= computed) + diff = clock - computed; + else + diff = computed - clock; + + if (diff < min) { + min = diff; + pparms->div_ref = div_ref; + pparms->div_out = div_out; + pparms->loopc = loopc; + + if (diff == 0) { + *pout = *pparms; + return 0; + } + } + } + } + } + + /* still acceptable */ + if (min < tolerance) { + *pout = *pparms; + return 0; + } + + drm_dbg(this->ddev, "can't find suitable params for %u khz\n", clock); + + return min; +} + +/* Pixel pll hardware related ops, per display pipe */ + +static void __pixpll_rreg(struct lsdc_pixpll *this, + union lsdc_pixpll_reg_bitmap *dst) +{ +#if defined(CONFIG_64BIT) + dst->d = readq(this->mmio); +#else + dst->w[0] = readl(this->mmio); + dst->w[1] = readl(this->mmio + 4); +#endif +} + +static void __pixpll_wreg(struct lsdc_pixpll *this, + union lsdc_pixpll_reg_bitmap *src) +{ +#if defined(CONFIG_64BIT) + writeq(src->d, this->mmio); +#else + writel(src->w[0], this->mmio); + writel(src->w[1], this->mmio + 4); +#endif +} + +static void __pixpll_ops_powerup(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.powerdown = 0; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_powerdown(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.powerdown = 1; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_on(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.sel_out = 1; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_off(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.sel_out = 0; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_bypass(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.bypass = 1; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_unbypass(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.bypass = 0; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_untoggle_param(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.set_param = 0; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_set_param(struct lsdc_pixpll * const this, + struct lsdc_pixpll_parms const *p) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.div_ref = p->div_ref; + pixpll_reg.bitmap.loopc = p->loopc; + pixpll_reg.bitmap.div_out = p->div_out; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_toggle_param(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + + __pixpll_rreg(this, &pixpll_reg); + + pixpll_reg.bitmap.set_param = 1; + + __pixpll_wreg(this, &pixpll_reg); +} + +static void __pixpll_ops_wait_locked(struct lsdc_pixpll * const this) +{ + union lsdc_pixpll_reg_bitmap pixpll_reg; + unsigned int counter = 0; + + do { + __pixpll_rreg(this, &pixpll_reg); + + if (pixpll_reg.bitmap.locked) + break; + + ++counter; + } while (counter < 2000); + + drm_dbg(this->ddev, "%u loop waited\n", counter); +} + +/* + * Update the PLL parameters to the PLL hardware + * + * @this: point to the object from which this function is called + * @pin: point to the struct of lsdc_pixpll_parms passed in + * + * return 0 if successful. + */ +static int lsdc_pixpll_update(struct lsdc_pixpll * const this, + struct lsdc_pixpll_parms const *pin) +{ + __pixpll_ops_bypass(this); + + __pixpll_ops_off(this); + + __pixpll_ops_powerdown(this); + + __pixpll_ops_toggle_param(this); + + __pixpll_ops_set_param(this, pin); + + __pixpll_ops_untoggle_param(this); + + __pixpll_ops_powerup(this); + + udelay(2); + + __pixpll_ops_wait_locked(this); + + __pixpll_ops_on(this); + + __pixpll_ops_unbypass(this); + + return 0; +} + +static unsigned int lsdc_pixpll_get_freq(struct lsdc_pixpll * const this) +{ + struct lsdc_pixpll_parms *ppar = this->priv; + union lsdc_pixpll_reg_bitmap pix_pll_reg; + unsigned int freq; + + __pixpll_rreg(this, &pix_pll_reg); + + ppar->div_ref = pix_pll_reg.bitmap.div_ref; + ppar->loopc = pix_pll_reg.bitmap.loopc; + ppar->div_out = pix_pll_reg.bitmap.div_out; + + freq = ppar->ref_clock / ppar->div_ref * ppar->loopc / ppar->div_out; + + return freq; +} + +static void lsdc_pixpll_print(struct lsdc_pixpll * const this, + struct drm_printer *p) +{ + struct lsdc_pixpll_parms *parms = this->priv; + + drm_printf(p, "div_ref: %u, loopc: %u, div_out: %u\n", + parms->div_ref, parms->loopc, parms->div_out); +} + +/* + * LS7A1000, LS7A2000 and ls2k2000's pixel pll setting register is same, + * we take this as default, create a new instance if a different model is + * introduced. + */ +static const struct lsdc_pixpll_funcs __pixpll_default_funcs = { + .setup = lsdc_pixel_pll_setup, + .compute = lsdc_pixel_pll_compute, + .update = lsdc_pixpll_update, + .get_rate = lsdc_pixpll_get_freq, + .print = lsdc_pixpll_print, +}; + +/* pixel pll initialization */ + +int lsdc_pixpll_init(struct lsdc_pixpll * const this, + struct drm_device *ddev, + unsigned int index) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + const struct loongson_gfx_desc *gfx = to_loongson_gfx(descp); + + this->ddev = ddev; + this->reg_size = 8; + this->reg_base = gfx->conf_reg_base + gfx->pixpll[index].reg_offset; + this->funcs = &__pixpll_default_funcs; + + return this->funcs->setup(this); +} diff --git a/drivers/gpu/drm/loongson/lsdc_pixpll.h b/drivers/gpu/drm/loongson/lsdc_pixpll.h new file mode 100644 index 0000000000..ec3486d90a --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_pixpll.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_PIXPLL_H__ +#define __LSDC_PIXPLL_H__ + +#include <drm/drm_device.h> + +/* + * Loongson 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 | + * | | + * +---- bypass (bypass above software configurable clock if set) ----+ + * + * outclk = refclk / div_ref * loopc / div_out; + * + * sel_out: PLL clock output selector(enable). + * + * If sel_out == 1, then enable output clock (turn On); + * If sel_out == 0, then disable output clock (turn Off); + * + * PLL working requirements: + * + * 1) 20 MHz <= refclk / div_ref <= 40Mhz + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz + */ + +struct lsdc_pixpll_parms { + unsigned int ref_clock; + unsigned int div_ref; + unsigned int loopc; + unsigned int div_out; +}; + +struct lsdc_pixpll; + +struct lsdc_pixpll_funcs { + int (*setup)(struct lsdc_pixpll * const this); + + int (*compute)(struct lsdc_pixpll * const this, + unsigned int clock, + struct lsdc_pixpll_parms *pout); + + int (*update)(struct lsdc_pixpll * const this, + struct lsdc_pixpll_parms const *pin); + + unsigned int (*get_rate)(struct lsdc_pixpll * const this); + + void (*print)(struct lsdc_pixpll * const this, + struct drm_printer *printer); +}; + +struct lsdc_pixpll { + const struct lsdc_pixpll_funcs *funcs; + + struct drm_device *ddev; + + /* PLL register offset */ + u32 reg_base; + /* PLL register size in bytes */ + u32 reg_size; + + void __iomem *mmio; + + struct lsdc_pixpll_parms *priv; +}; + +int lsdc_pixpll_init(struct lsdc_pixpll * const this, + struct drm_device *ddev, + unsigned int index); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_plane.c b/drivers/gpu/drm/loongson/lsdc_plane.c new file mode 100644 index 0000000000..0d50946332 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_plane.c @@ -0,0 +1,793 @@ +// 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_framebuffer.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_plane_helper.h> + +#include "lsdc_drv.h" +#include "lsdc_regs.h" +#include "lsdc_ttm.h" + +static const u32 lsdc_primary_formats[] = { + DRM_FORMAT_XRGB8888, +}; + +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 unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, + struct drm_plane_state *state) +{ + unsigned int offset = fb->offsets[0]; + + offset += fb->format->cpp[0] * (state->src_x >> 16); + offset += fb->pitches[0] * (state->src_y >> 16); + + return offset; +} + +static u64 lsdc_fb_base_addr(struct drm_framebuffer *fb) +{ + struct lsdc_device *ldev = to_lsdc(fb->dev); + struct lsdc_bo *lbo = gem_to_lsdc_bo(fb->obj[0]); + + return lsdc_bo_gpu_offset(lbo) + ldev->vram_base; +} + +static int lsdc_primary_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_crtc_state *new_crtc_state; + + if (!crtc) + return 0; + + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + return drm_atomic_helper_check_plane_state(new_plane_state, + new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, true); +} + +static void lsdc_primary_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_primary *primary = to_lsdc_primary(plane); + const struct lsdc_primary_plane_ops *ops = primary->ops; + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_framebuffer *old_fb = old_plane_state->fb; + u64 fb_addr = lsdc_fb_base_addr(new_fb); + + fb_addr += lsdc_get_fb_offset(new_fb, new_plane_state); + + ops->update_fb_addr(primary, fb_addr); + ops->update_fb_stride(primary, new_fb->pitches[0]); + + if (!old_fb || old_fb->format != new_fb->format) + ops->update_fb_format(primary, new_fb->format); +} + +static void lsdc_primary_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + /* + * Do nothing, just prevent call into atomic_update(). + * Writing the format as LSDC_PF_NONE can disable the primary, + * But it seems not necessary... + */ + 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 drm_framebuffer *fb = new_state->fb; + struct lsdc_bo *lbo; + u64 gpu_vaddr; + int ret; + + if (!fb) + return 0; + + lbo = gem_to_lsdc_bo(fb->obj[0]); + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) { + drm_err(plane->dev, "bo %p reserve failed\n", lbo); + return ret; + } + + ret = lsdc_bo_pin(lbo, LSDC_GEM_DOMAIN_VRAM, &gpu_vaddr); + + lsdc_bo_unreserve(lbo); + + if (unlikely(ret)) { + drm_err(plane->dev, "bo %p pin failed\n", lbo); + return ret; + } + + lsdc_bo_ref(lbo); + + if (plane->type != DRM_PLANE_TYPE_CURSOR) + drm_dbg(plane->dev, + "%s[%p] pin at 0x%llx, bo size: %zu\n", + plane->name, lbo, gpu_vaddr, lsdc_bo_size(lbo)); + + return drm_gem_plane_helper_prepare_fb(plane, new_state); +} + +static void lsdc_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_framebuffer *fb = old_state->fb; + struct lsdc_bo *lbo; + int ret; + + if (!fb) + return; + + lbo = gem_to_lsdc_bo(fb->obj[0]); + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) { + drm_err(plane->dev, "%p reserve failed\n", lbo); + return; + } + + lsdc_bo_unpin(lbo); + + lsdc_bo_unreserve(lbo); + + lsdc_bo_unref(lbo); + + if (plane->type != DRM_PLANE_TYPE_CURSOR) + drm_dbg(plane->dev, "%s unpin\n", plane->name); +} + +static const struct drm_plane_helper_funcs lsdc_primary_helper_funcs = { + .prepare_fb = lsdc_plane_prepare_fb, + .cleanup_fb = lsdc_plane_cleanup_fb, + .atomic_check = lsdc_primary_atomic_check, + .atomic_update = lsdc_primary_atomic_update, + .atomic_disable = lsdc_primary_atomic_disable, +}; + +static int lsdc_cursor_plane_atomic_async_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_state; + struct drm_crtc_state *crtc_state; + + new_state = drm_atomic_get_new_plane_state(state, plane); + + if (!plane->state || !plane->state->fb) { + drm_dbg(plane->dev, "%s: state is NULL\n", plane->name); + return -EINVAL; + } + + if (new_state->crtc_w != new_state->crtc_h) { + drm_dbg(plane->dev, "unsupported cursor size: %ux%u\n", + new_state->crtc_w, new_state->crtc_h); + return -EINVAL; + } + + if (new_state->crtc_w != 64 && new_state->crtc_w != 32) { + drm_dbg(plane->dev, "unsupported cursor size: %ux%u\n", + new_state->crtc_w, new_state->crtc_h); + return -EINVAL; + } + + crtc_state = drm_atomic_get_existing_crtc_state(state, new_state->crtc); + if (!crtc_state->active) + return -EINVAL; + + if (plane->state->crtc != new_state->crtc || + plane->state->src_w != new_state->src_w || + plane->state->src_h != new_state->src_h || + plane->state->crtc_w != new_state->crtc_w || + plane->state->crtc_h != new_state->crtc_h) + return -EINVAL; + + if (new_state->visible != plane->state->visible) + return -EINVAL; + + return drm_atomic_helper_check_plane_state(plane->state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); +} + +static void lsdc_cursor_plane_atomic_async_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + const struct lsdc_cursor_plane_ops *ops = cursor->ops; + struct drm_framebuffer *old_fb = plane->state->fb; + struct drm_framebuffer *new_fb; + struct drm_plane_state *new_state; + + new_state = drm_atomic_get_new_plane_state(state, plane); + + new_fb = plane->state->fb; + + plane->state->crtc_x = new_state->crtc_x; + plane->state->crtc_y = new_state->crtc_y; + plane->state->crtc_h = new_state->crtc_h; + plane->state->crtc_w = new_state->crtc_w; + plane->state->src_x = new_state->src_x; + plane->state->src_y = new_state->src_y; + plane->state->src_h = new_state->src_h; + plane->state->src_w = new_state->src_w; + swap(plane->state->fb, new_state->fb); + + if (new_state->visible) { + enum lsdc_cursor_size cursor_size; + + switch (new_state->crtc_w) { + case 64: + cursor_size = CURSOR_SIZE_64X64; + break; + case 32: + cursor_size = CURSOR_SIZE_32X32; + break; + default: + cursor_size = CURSOR_SIZE_32X32; + break; + } + + ops->update_position(cursor, new_state->crtc_x, new_state->crtc_y); + + ops->update_cfg(cursor, cursor_size, CURSOR_FORMAT_ARGB8888); + + if (!old_fb || old_fb != new_fb) + ops->update_bo_addr(cursor, lsdc_fb_base_addr(new_fb)); + } +} + +/* ls7a1000 cursor plane helpers */ + +static int ls7a1000_cursor_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + + crtc = new_plane_state->crtc; + if (!crtc) { + drm_dbg(plane->dev, "%s is not bind to a crtc\n", plane->name); + return 0; + } + + if (new_plane_state->crtc_w != 32 || new_plane_state->crtc_h != 32) { + drm_dbg(plane->dev, "unsupported cursor size: %ux%u\n", + new_plane_state->crtc_w, new_plane_state->crtc_h); + return -EINVAL; + } + + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + return drm_atomic_helper_check_plane_state(new_plane_state, + new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); +} + +static void ls7a1000_cursor_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_framebuffer *old_fb = old_plane_state->fb; + const struct lsdc_cursor_plane_ops *ops = cursor->ops; + u64 addr = lsdc_fb_base_addr(new_fb); + + if (!new_plane_state->visible) + return; + + ops->update_position(cursor, new_plane_state->crtc_x, new_plane_state->crtc_y); + + if (!old_fb || old_fb != new_fb) + ops->update_bo_addr(cursor, addr); + + ops->update_cfg(cursor, CURSOR_SIZE_32X32, CURSOR_FORMAT_ARGB8888); +} + +static void ls7a1000_cursor_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + const struct lsdc_cursor_plane_ops *ops = cursor->ops; + + ops->update_cfg(cursor, CURSOR_SIZE_32X32, CURSOR_FORMAT_DISABLE); +} + +static const struct drm_plane_helper_funcs ls7a1000_cursor_plane_helper_funcs = { + .prepare_fb = lsdc_plane_prepare_fb, + .cleanup_fb = lsdc_plane_cleanup_fb, + .atomic_check = ls7a1000_cursor_plane_atomic_check, + .atomic_update = ls7a1000_cursor_plane_atomic_update, + .atomic_disable = ls7a1000_cursor_plane_atomic_disable, + .atomic_async_check = lsdc_cursor_plane_atomic_async_check, + .atomic_async_update = lsdc_cursor_plane_atomic_async_update, +}; + +/* ls7a2000 cursor plane helpers */ + +static int ls7a2000_cursor_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state; + struct drm_crtc_state *new_crtc_state; + struct drm_crtc *crtc; + + new_plane_state = drm_atomic_get_new_plane_state(state, plane); + + crtc = new_plane_state->crtc; + if (!crtc) { + drm_dbg(plane->dev, "%s is not bind to a crtc\n", plane->name); + return 0; + } + + if (new_plane_state->crtc_w != new_plane_state->crtc_h) { + drm_dbg(plane->dev, "unsupported cursor size: %ux%u\n", + new_plane_state->crtc_w, new_plane_state->crtc_h); + return -EINVAL; + } + + if (new_plane_state->crtc_w != 64 && new_plane_state->crtc_w != 32) { + drm_dbg(plane->dev, "unsupported cursor size: %ux%u\n", + new_plane_state->crtc_w, new_plane_state->crtc_h); + return -EINVAL; + } + + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + return drm_atomic_helper_check_plane_state(new_plane_state, + new_crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, true); +} + +/* Update the format, size and location of the cursor */ + +static void ls7a2000_cursor_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_framebuffer *old_fb = old_plane_state->fb; + const struct lsdc_cursor_plane_ops *ops = cursor->ops; + enum lsdc_cursor_size cursor_size; + + if (!new_plane_state->visible) + return; + + ops->update_position(cursor, new_plane_state->crtc_x, new_plane_state->crtc_y); + + if (!old_fb || new_fb != old_fb) { + u64 addr = lsdc_fb_base_addr(new_fb); + + ops->update_bo_addr(cursor, addr); + } + + switch (new_plane_state->crtc_w) { + case 64: + cursor_size = CURSOR_SIZE_64X64; + break; + case 32: + cursor_size = CURSOR_SIZE_32X32; + break; + default: + cursor_size = CURSOR_SIZE_64X64; + break; + } + + ops->update_cfg(cursor, cursor_size, CURSOR_FORMAT_ARGB8888); +} + +static void ls7a2000_cursor_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + const struct lsdc_cursor_plane_ops *hw_ops = cursor->ops; + + hw_ops->update_cfg(cursor, CURSOR_SIZE_64X64, CURSOR_FORMAT_DISABLE); +} + +static const struct drm_plane_helper_funcs ls7a2000_cursor_plane_helper_funcs = { + .prepare_fb = lsdc_plane_prepare_fb, + .cleanup_fb = lsdc_plane_cleanup_fb, + .atomic_check = ls7a2000_cursor_plane_atomic_check, + .atomic_update = ls7a2000_cursor_plane_atomic_update, + .atomic_disable = ls7a2000_cursor_plane_atomic_disable, + .atomic_async_check = lsdc_cursor_plane_atomic_async_check, + .atomic_async_update = lsdc_cursor_plane_atomic_async_update, +}; + +static void lsdc_plane_atomic_print_state(struct drm_printer *p, + const struct drm_plane_state *state) +{ + struct drm_framebuffer *fb = state->fb; + u64 addr; + + if (!fb) + return; + + addr = lsdc_fb_base_addr(fb); + + drm_printf(p, "\tdma addr=%llx\n", addr); +} + +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 = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_print_state = lsdc_plane_atomic_print_state, +}; + +/* Primary plane 0 hardware related ops */ + +static void lsdc_primary0_update_fb_addr(struct lsdc_primary *primary, u64 addr) +{ + struct lsdc_device *ldev = primary->ldev; + u32 status; + u32 lo, hi; + + /* 40-bit width physical address bus */ + lo = addr & 0xFFFFFFFF; + hi = (addr >> 32) & 0xFF; + + status = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + if (status & FB_REG_IN_USING) { + lsdc_wreg32(ldev, LSDC_CRTC0_FB1_ADDR_LO_REG, lo); + lsdc_wreg32(ldev, LSDC_CRTC0_FB1_ADDR_HI_REG, hi); + } else { + lsdc_wreg32(ldev, LSDC_CRTC0_FB0_ADDR_LO_REG, lo); + lsdc_wreg32(ldev, LSDC_CRTC0_FB0_ADDR_HI_REG, hi); + } +} + +static void lsdc_primary0_update_fb_stride(struct lsdc_primary *primary, u32 stride) +{ + struct lsdc_device *ldev = primary->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); +} + +static void lsdc_primary0_update_fb_format(struct lsdc_primary *primary, + const struct drm_format_info *format) +{ + struct lsdc_device *ldev = primary->ldev; + u32 status; + + status = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + /* + * TODO: add RGB565 support, only support XRBG8888 at present + */ + status &= ~CFG_PIX_FMT_MASK; + status |= LSDC_PF_XRGB8888; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, status); +} + +/* Primary plane 1 hardware related ops */ + +static void lsdc_primary1_update_fb_addr(struct lsdc_primary *primary, u64 addr) +{ + struct lsdc_device *ldev = primary->ldev; + u32 status; + u32 lo, hi; + + /* 40-bit width physical address bus */ + lo = addr & 0xFFFFFFFF; + hi = (addr >> 32) & 0xFF; + + status = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (status & FB_REG_IN_USING) { + lsdc_wreg32(ldev, LSDC_CRTC1_FB1_ADDR_LO_REG, lo); + lsdc_wreg32(ldev, LSDC_CRTC1_FB1_ADDR_HI_REG, hi); + } else { + lsdc_wreg32(ldev, LSDC_CRTC1_FB0_ADDR_LO_REG, lo); + lsdc_wreg32(ldev, LSDC_CRTC1_FB0_ADDR_HI_REG, hi); + } +} + +static void lsdc_primary1_update_fb_stride(struct lsdc_primary *primary, u32 stride) +{ + struct lsdc_device *ldev = primary->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); +} + +static void lsdc_primary1_update_fb_format(struct lsdc_primary *primary, + const struct drm_format_info *format) +{ + struct lsdc_device *ldev = primary->ldev; + u32 status; + + status = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + /* + * TODO: add RGB565 support, only support XRBG8888 at present + */ + status &= ~CFG_PIX_FMT_MASK; + status |= LSDC_PF_XRGB8888; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, status); +} + +static const struct lsdc_primary_plane_ops lsdc_primary_plane_hw_ops[2] = { + { + .update_fb_addr = lsdc_primary0_update_fb_addr, + .update_fb_stride = lsdc_primary0_update_fb_stride, + .update_fb_format = lsdc_primary0_update_fb_format, + }, + { + .update_fb_addr = lsdc_primary1_update_fb_addr, + .update_fb_stride = lsdc_primary1_update_fb_stride, + .update_fb_format = lsdc_primary1_update_fb_format, + }, +}; + +/* + * Update location, format, enable and disable state of the cursor, + * For those who have two hardware cursor, let cursor 0 is attach to CRTC-0, + * cursor 1 is attach to CRTC-1. Compositing the primary plane and cursor + * plane is automatically done by hardware, the cursor is alway on the top of + * the primary plane. In other word, z-order is fixed in hardware and cannot + * be changed. For those old DC who has only one hardware cursor, we made it + * shared by the two screen, this works on extend screen mode. + */ + +/* cursor plane 0 (for pipe 0) related hardware ops */ + +static void lsdc_cursor0_update_bo_addr(struct lsdc_cursor *cursor, u64 addr) +{ + struct lsdc_device *ldev = cursor->ldev; + + /* 40-bit width physical address bus */ + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (addr >> 32) & 0xFF); + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, addr); +} + +static void lsdc_cursor0_update_position(struct lsdc_cursor *cursor, int x, int y) +{ + struct lsdc_device *ldev = cursor->ldev; + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); +} + +static void lsdc_cursor0_update_cfg(struct lsdc_cursor *cursor, + enum lsdc_cursor_size cursor_size, + enum lsdc_cursor_format fmt) +{ + struct lsdc_device *ldev = cursor->ldev; + u32 cfg; + + cfg = CURSOR_ON_CRTC0 << CURSOR_LOCATION_SHIFT | + cursor_size << CURSOR_SIZE_SHIFT | + fmt << CURSOR_FORMAT_SHIFT; + + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, cfg); +} + +/* cursor plane 1 (for pipe 1) related hardware ops */ + +static void lsdc_cursor1_update_bo_addr(struct lsdc_cursor *cursor, u64 addr) +{ + struct lsdc_device *ldev = cursor->ldev; + + /* 40-bit width physical address bus */ + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (addr >> 32) & 0xFF); + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, addr); +} + +static void lsdc_cursor1_update_position(struct lsdc_cursor *cursor, int x, int y) +{ + struct lsdc_device *ldev = cursor->ldev; + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); +} + +static void lsdc_cursor1_update_cfg(struct lsdc_cursor *cursor, + enum lsdc_cursor_size cursor_size, + enum lsdc_cursor_format fmt) +{ + struct lsdc_device *ldev = cursor->ldev; + u32 cfg; + + cfg = CURSOR_ON_CRTC1 << CURSOR_LOCATION_SHIFT | + cursor_size << CURSOR_SIZE_SHIFT | + fmt << CURSOR_FORMAT_SHIFT; + + lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, cfg); +} + +/* The hardware cursors become normal since ls7a2000/ls2k2000 */ + +static const struct lsdc_cursor_plane_ops ls7a2000_cursor_hw_ops[2] = { + { + .update_bo_addr = lsdc_cursor0_update_bo_addr, + .update_cfg = lsdc_cursor0_update_cfg, + .update_position = lsdc_cursor0_update_position, + }, + { + .update_bo_addr = lsdc_cursor1_update_bo_addr, + .update_cfg = lsdc_cursor1_update_cfg, + .update_position = lsdc_cursor1_update_position, + }, +}; + +/* Quirks for cursor 1, only for old loongson display controller */ + +static void lsdc_cursor1_update_bo_addr_quirk(struct lsdc_cursor *cursor, u64 addr) +{ + struct lsdc_device *ldev = cursor->ldev; + + /* 40-bit width physical address bus */ + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (addr >> 32) & 0xFF); + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, addr); +} + +static void lsdc_cursor1_update_position_quirk(struct lsdc_cursor *cursor, int x, int y) +{ + struct lsdc_device *ldev = cursor->ldev; + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); +} + +static void lsdc_cursor1_update_cfg_quirk(struct lsdc_cursor *cursor, + enum lsdc_cursor_size cursor_size, + enum lsdc_cursor_format fmt) +{ + struct lsdc_device *ldev = cursor->ldev; + u32 cfg; + + cfg = CURSOR_ON_CRTC1 << CURSOR_LOCATION_SHIFT | + cursor_size << CURSOR_SIZE_SHIFT | + fmt << CURSOR_FORMAT_SHIFT; + + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, cfg); +} + +/* + * The unforgiving LS7A1000/LS2K1000 has only one hardware cursors plane + */ +static const struct lsdc_cursor_plane_ops ls7a1000_cursor_hw_ops[2] = { + { + .update_bo_addr = lsdc_cursor0_update_bo_addr, + .update_cfg = lsdc_cursor0_update_cfg, + .update_position = lsdc_cursor0_update_position, + }, + { + .update_bo_addr = lsdc_cursor1_update_bo_addr_quirk, + .update_cfg = lsdc_cursor1_update_cfg_quirk, + .update_position = lsdc_cursor1_update_position_quirk, + }, +}; + +int lsdc_primary_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index) +{ + struct lsdc_primary *primary = to_lsdc_primary(plane); + int ret; + + ret = drm_universal_plane_init(ddev, plane, 1 << index, + &lsdc_plane_funcs, + lsdc_primary_formats, + ARRAY_SIZE(lsdc_primary_formats), + lsdc_fb_format_modifiers, + DRM_PLANE_TYPE_PRIMARY, + "ls-primary-plane-%u", index); + if (ret) + return ret; + + drm_plane_helper_add(plane, &lsdc_primary_helper_funcs); + + primary->ldev = to_lsdc(ddev); + primary->ops = &lsdc_primary_plane_hw_ops[index]; + + return 0; +} + +int ls7a1000_cursor_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + int ret; + + ret = drm_universal_plane_init(ddev, plane, 1 << index, + &lsdc_plane_funcs, + lsdc_cursor_formats, + ARRAY_SIZE(lsdc_cursor_formats), + lsdc_fb_format_modifiers, + DRM_PLANE_TYPE_CURSOR, + "ls-cursor-plane-%u", index); + if (ret) + return ret; + + cursor->ldev = to_lsdc(ddev); + cursor->ops = &ls7a1000_cursor_hw_ops[index]; + + drm_plane_helper_add(plane, &ls7a1000_cursor_plane_helper_funcs); + + return 0; +} + +int ls7a2000_cursor_plane_init(struct drm_device *ddev, + struct drm_plane *plane, + unsigned int index) +{ + struct lsdc_cursor *cursor = to_lsdc_cursor(plane); + int ret; + + ret = drm_universal_plane_init(ddev, plane, 1 << index, + &lsdc_plane_funcs, + lsdc_cursor_formats, + ARRAY_SIZE(lsdc_cursor_formats), + lsdc_fb_format_modifiers, + DRM_PLANE_TYPE_CURSOR, + "ls-cursor-plane-%u", index); + if (ret) + return ret; + + cursor->ldev = to_lsdc(ddev); + cursor->ops = &ls7a2000_cursor_hw_ops[index]; + + drm_plane_helper_add(plane, &ls7a2000_cursor_plane_helper_funcs); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_probe.c b/drivers/gpu/drm/loongson/lsdc_probe.c new file mode 100644 index 0000000000..48ba69bb8a --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_probe.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include "lsdc_drv.h" +#include "lsdc_probe.h" + +/* + * Processor ID (implementation) values for bits 15:8 of the PRID register. + */ +#define LOONGSON_CPU_IMP_MASK 0xff00 +#define LOONGSON_CPU_IMP_SHIFT 8 + +#define LOONGARCH_CPU_IMP_LS2K1000 0xa0 +#define LOONGARCH_CPU_IMP_LS2K2000 0xb0 +#define LOONGARCH_CPU_IMP_LS3A5000 0xc0 + +#define LOONGSON_CPU_MIPS_IMP_LS2K 0x61 /* Loongson 2K Mips series SoC */ + +/* + * Particular Revision values for bits 7:0 of the PRID register. + */ +#define LOONGSON_CPU_REV_MASK 0x00ff + +#define LOONGARCH_CPUCFG_PRID_REG 0x0 + +/* + * We can achieve fine-grained control with the information about the host. + */ + +unsigned int loongson_cpu_get_prid(u8 *imp, u8 *rev) +{ + unsigned int prid = 0; + +#if defined(__loongarch__) + __asm__ volatile("cpucfg %0, %1\n\t" + : "=&r"(prid) + : "r"(LOONGARCH_CPUCFG_PRID_REG) + ); +#endif + +#if defined(__mips__) + __asm__ volatile("mfc0\t%0, $15\n\t" + : "=r" (prid) + ); +#endif + + if (imp) + *imp = (prid & LOONGSON_CPU_IMP_MASK) >> LOONGSON_CPU_IMP_SHIFT; + + if (rev) + *rev = prid & LOONGSON_CPU_REV_MASK; + + return prid; +} diff --git a/drivers/gpu/drm/loongson/lsdc_probe.h b/drivers/gpu/drm/loongson/lsdc_probe.h new file mode 100644 index 0000000000..8bb6de2e3c --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_probe.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_PROBE_H__ +#define __LSDC_PROBE_H__ + +/* Helpers for chip detection */ +unsigned int loongson_cpu_get_prid(u8 *impl, u8 *rev); + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_regs.h b/drivers/gpu/drm/loongson/lsdc_regs.h new file mode 100644 index 0000000000..e8ea28689c --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_regs.h @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_REGS_H__ +#define __LSDC_REGS_H__ + +#include <linux/bitops.h> +#include <linux/types.h> + +/* + * PIXEL PLL Reference clock + */ +#define LSDC_PLL_REF_CLK_KHZ 100000 + +/* + * Those PLL registers are relative to LSxxxxx_CFG_REG_BASE. xxxxx = 7A1000, + * 7A2000, 2K2000, 2K1000 etc. + */ + +/* LS7A1000 */ + +#define LS7A1000_PIXPLL0_REG 0x04B0 +#define LS7A1000_PIXPLL1_REG 0x04C0 + +/* The DC, GPU, Graphic Memory Controller share the single gfxpll */ +#define LS7A1000_PLL_GFX_REG 0x0490 + +#define LS7A1000_CONF_REG_BASE 0x10010000 + +/* LS7A2000 */ + +#define LS7A2000_PIXPLL0_REG 0x04B0 +#define LS7A2000_PIXPLL1_REG 0x04C0 + +/* The DC, GPU, Graphic Memory Controller share the single gfxpll */ +#define LS7A2000_PLL_GFX_REG 0x0490 + +#define LS7A2000_CONF_REG_BASE 0x10010000 + +/* For LSDC_CRTCx_CFG_REG */ +#define CFG_PIX_FMT_MASK GENMASK(2, 0) + +enum lsdc_pixel_format { + LSDC_PF_NONE = 0, + LSDC_PF_XRGB444 = 1, /* [12 bits] */ + LSDC_PF_XRGB555 = 2, /* [15 bits] */ + LSDC_PF_XRGB565 = 3, /* RGB [16 bits] */ + LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ +}; + +/* + * Each crtc has two set fb address registers usable, FB_REG_IN_USING bit of + * LSDC_CRTCx_CFG_REG indicate which fb address register is in using by the + * CRTC currently. CFG_PAGE_FLIP is used to trigger the switch, the switching + * will be finished at the very next vblank. Trigger it again if you want to + * switch back. + * + * If FB0_ADDR_REG is in using, we write the address to FB0_ADDR_REG, + * if FB1_ADDR_REG is in using, we write the address to FB1_ADDR_REG. + */ +#define CFG_PAGE_FLIP BIT(7) +#define CFG_OUTPUT_ENABLE BIT(8) +#define CFG_HW_CLONE BIT(9) +/* Indicate witch fb addr reg is in using, currently. read only */ +#define FB_REG_IN_USING BIT(11) +#define CFG_GAMMA_EN BIT(12) + +/* The DC get soft reset if this bit changed from "1" to "0", active low */ +#define CFG_RESET_N BIT(20) +/* If this bit is set, it say that the CRTC stop working anymore, anchored. */ +#define CRTC_ANCHORED BIT(24) + +/* + * The DMA step of the DC in LS7A2000/LS2K2000 is configurable, + * setting those bits on ls7a1000 platform make no effect. + */ +#define CFG_DMA_STEP_MASK GENMASK(17, 16) +#define CFG_DMA_STEP_SHIFT 16 +enum lsdc_dma_steps { + LSDC_DMA_STEP_256_BYTES = 0, + LSDC_DMA_STEP_128_BYTES = 1, + LSDC_DMA_STEP_64_BYTES = 2, + LSDC_DMA_STEP_32_BYTES = 3, +}; + +#define CFG_VALID_BITS_MASK GENMASK(20, 0) + +/* For LSDC_CRTCx_HSYNC_REG */ +#define HSYNC_INV BIT(31) +#define HSYNC_EN BIT(30) +#define HSYNC_END_MASK GENMASK(28, 16) +#define HSYNC_END_SHIFT 16 +#define HSYNC_START_MASK GENMASK(12, 0) +#define HSYNC_START_SHIFT 0 + +/* For LSDC_CRTCx_VSYNC_REG */ +#define VSYNC_INV BIT(31) +#define VSYNC_EN BIT(30) +#define VSYNC_END_MASK GENMASK(27, 16) +#define VSYNC_END_SHIFT 16 +#define VSYNC_START_MASK GENMASK(11, 0) +#define VSYNC_START_SHIFT 0 + +/*********** CRTC0 ***********/ +#define LSDC_CRTC0_CFG_REG 0x1240 +#define LSDC_CRTC0_FB0_ADDR_LO_REG 0x1260 +#define LSDC_CRTC0_FB0_ADDR_HI_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 +#define LSDC_CRTC0_FB1_ADDR_LO_REG 0x1580 +#define LSDC_CRTC0_FB1_ADDR_HI_REG 0x15C0 + +/*********** CRTC1 ***********/ +#define LSDC_CRTC1_CFG_REG 0x1250 +#define LSDC_CRTC1_FB0_ADDR_LO_REG 0x1270 +#define LSDC_CRTC1_FB0_ADDR_HI_REG 0x15B0 +#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_CRTC1_FB1_ADDR_LO_REG 0x1590 +#define LSDC_CRTC1_FB1_ADDR_HI_REG 0x15D0 + +/* For LSDC_CRTCx_DVO_CONF_REG */ +#define PHY_CLOCK_POL BIT(9) +#define PHY_CLOCK_EN BIT(8) +#define PHY_DE_POL BIT(1) +#define PHY_DATA_EN BIT(0) + +/*********** DVO0 ***********/ +#define LSDC_CRTC0_DVO_CONF_REG 0x13C0 + +/*********** DVO1 ***********/ +#define LSDC_CRTC1_DVO_CONF_REG 0x13D0 + +/* + * All of the DC variants has the hardware which record the scan position + * of the CRTC, [31:16] : current X position, [15:0] : current Y position + */ +#define LSDC_CRTC0_SCAN_POS_REG 0x14C0 +#define LSDC_CRTC1_SCAN_POS_REG 0x14D0 + +/* + * LS7A2000 has Sync Deviation register. + */ +#define SYNC_DEVIATION_EN BIT(31) +#define SYNC_DEVIATION_NUM GENMASK(12, 0) +#define LSDC_CRTC0_SYNC_DEVIATION_REG 0x1B80 +#define LSDC_CRTC1_SYNC_DEVIATION_REG 0x1B90 + +/* + * In gross, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10, but not all of + * the registers obey this rule, LSDC_CURSORx_XXX_REG just don't honor this. + * This is the root cause we can't untangle the code by manpulating offset + * of the register access simply. Our hardware engineers are lack experiance + * when they design this... + */ +#define CRTC_PIPE_OFFSET 0x10 + +/* + * There is only one hardware cursor unit in LS7A1000 and LS2K1000, let + * CFG_HW_CLONE_EN bit be "1" could eliminate this embarrassment, we made + * it on custom clone mode application. While LS7A2000 has two hardware + * cursor unit which is good enough. + */ +#define CURSOR_FORMAT_MASK GENMASK(1, 0) +#define CURSOR_FORMAT_SHIFT 0 +enum lsdc_cursor_format { + CURSOR_FORMAT_DISABLE = 0, + CURSOR_FORMAT_MONOCHROME = 1, /* masked */ + CURSOR_FORMAT_ARGB8888 = 2, /* A8R8G8B8 */ +}; + +/* + * LS7A1000 and LS2K1000 only support 32x32, LS2K2000 and LS7A2000 support + * 64x64, but it seems that setting this bit make no harms on LS7A1000, it + * just don't take effects. + */ +#define CURSOR_SIZE_SHIFT 2 +enum lsdc_cursor_size { + CURSOR_SIZE_32X32 = 0, + CURSOR_SIZE_64X64 = 1, +}; + +#define CURSOR_LOCATION_SHIFT 4 +enum lsdc_cursor_location { + CURSOR_ON_CRTC0 = 0, + CURSOR_ON_CRTC1 = 1, +}; + +#define LSDC_CURSOR0_CFG_REG 0x1520 +#define LSDC_CURSOR0_ADDR_LO_REG 0x1530 +#define LSDC_CURSOR0_ADDR_HI_REG 0x15e0 +#define LSDC_CURSOR0_POSITION_REG 0x1540 /* [31:16] Y, [15:0] X */ +#define LSDC_CURSOR0_BG_COLOR_REG 0x1550 /* background color */ +#define LSDC_CURSOR0_FG_COLOR_REG 0x1560 /* foreground color */ + +#define LSDC_CURSOR1_CFG_REG 0x1670 +#define LSDC_CURSOR1_ADDR_LO_REG 0x1680 +#define LSDC_CURSOR1_ADDR_HI_REG 0x16e0 +#define LSDC_CURSOR1_POSITION_REG 0x1690 /* [31:16] Y, [15:0] X */ +#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 15:0 inidicate the interrupt status + * Bits 31:16 control enable interrupts corresponding to bit 15:0 or not + * Write 1 to enable, write 0 to disable + * + * RF: Read Finished + * IDBU: Internal Data Buffer Underflow + * IDBFU: Internal Data Buffer Fatal Underflow + * CBRF: Cursor Buffer Read Finished Flag, no use. + * FBRF0: CRTC-0 reading from its framebuffer finished. + * FBRF1: CRTC-1 reading from its framebuffer finished. + * + * +-------+--------------------------+-------+--------+--------+-------+ + * | 31:27 | 26:16 | 15:11 | 10 | 9 | 8 | + * +-------+--------------------------+-------+--------+--------+-------+ + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | IDBU0 | + * +-------+--------------------------+-------+--------+--------+-------+ + * + * +-------+-------+-------+------+--------+--------+--------+--------+ + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * +-------+-------+-------+------+--------+--------+--------+--------+ + * | IDBU1 | FBRF0 | FBRF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | + * +-------+-------+-------+------+--------+--------+--------+--------+ + * + * unfortunately, CRTC0's interrupt is mess with CRTC1's interrupt in one + * register again. + */ + +#define LSDC_INT_REG 0x1570 + +#define INT_CRTC0_VSYNC BIT(2) +#define INT_CRTC0_HSYNC BIT(3) +#define INT_CRTC0_RF BIT(6) +#define INT_CRTC0_IDBU BIT(8) +#define INT_CRTC0_IDBFU BIT(10) + +#define INT_CRTC1_VSYNC BIT(0) +#define INT_CRTC1_HSYNC BIT(1) +#define INT_CRTC1_RF BIT(5) +#define INT_CRTC1_IDBU BIT(7) +#define INT_CRTC1_IDBFU BIT(9) + +#define INT_CRTC0_VSYNC_EN BIT(18) +#define INT_CRTC0_HSYNC_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_CRTC1_VSYNC_EN BIT(16) +#define INT_CRTC1_HSYNC_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(15, 0) + +/* + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. + * They are under control of the LS7A_DC_GPIO_DAT_REG and LS7A_DC_GPIO_DIR_REG + * register, Those GPIOs has no relationship whth the GPIO hardware on the + * bridge chip itself. Those offsets are relative to DC register base address + * + * LS2k1000 don't have those registers, they use hardware i2c or general GPIO + * emulated i2c from linux i2c subsystem. + * + * 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 + */ +#define LS7A_DC_GPIO_DIR_REG 0x1660 + +/* + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder + */ + +/* + * Number of continuous packets may be present + * in HDMI hblank and vblank zone, should >= 48 + */ +#define LSDC_HDMI0_ZONE_REG 0x1700 +#define LSDC_HDMI1_ZONE_REG 0x1710 + +#define HDMI_H_ZONE_IDLE_SHIFT 0 +#define HDMI_V_ZONE_IDLE_SHIFT 16 + +/* HDMI Iterface Control Reg */ +#define HDMI_INTERFACE_EN BIT(0) +#define HDMI_PACKET_EN BIT(1) +#define HDMI_AUDIO_EN BIT(2) +/* + * Preamble: + * Immediately preceding each video data period or data island period is the + * preamble. This is a sequence of eight identical control characters that + * indicate whether the upcoming data period is a video data period or is a + * data island. The values of CTL0, CTL1, CTL2, and CTL3 indicate the type of + * data period that follows. + */ +#define HDMI_VIDEO_PREAMBLE_MASK GENMASK(7, 4) +#define HDMI_VIDEO_PREAMBLE_SHIFT 4 +/* 1: hw i2c, 0: gpio emu i2c, shouldn't put in LSDC_HDMIx_INTF_CTRL_REG */ +#define HW_I2C_EN BIT(8) +#define HDMI_CTL_PERIOD_MODE BIT(9) +#define LSDC_HDMI0_INTF_CTRL_REG 0x1720 +#define LSDC_HDMI1_INTF_CTRL_REG 0x1730 + +#define HDMI_PHY_EN BIT(0) +#define HDMI_PHY_RESET_N BIT(1) +#define HDMI_PHY_TERM_L_EN BIT(8) +#define HDMI_PHY_TERM_H_EN BIT(9) +#define HDMI_PHY_TERM_DET_EN BIT(10) +#define HDMI_PHY_TERM_STATUS BIT(11) +#define LSDC_HDMI0_PHY_CTRL_REG 0x1800 +#define LSDC_HDMI1_PHY_CTRL_REG 0x1810 + +/* High level duration need > 1us */ +#define HDMI_PLL_ENABLE BIT(0) +#define HDMI_PLL_LOCKED BIT(16) +/* Bypass the software configured values, using default source from somewhere */ +#define HDMI_PLL_BYPASS BIT(17) + +#define HDMI_PLL_IDF_SHIFT 1 +#define HDMI_PLL_IDF_MASK GENMASK(5, 1) +#define HDMI_PLL_LF_SHIFT 6 +#define HDMI_PLL_LF_MASK GENMASK(12, 6) +#define HDMI_PLL_ODF_SHIFT 13 +#define HDMI_PLL_ODF_MASK GENMASK(15, 13) +#define LSDC_HDMI0_PHY_PLL_REG 0x1820 +#define LSDC_HDMI1_PHY_PLL_REG 0x1830 + +/* LS7A2000/LS2K2000 has hpd status reg, while the two hdmi's status + * located at the one register again. + */ +#define LSDC_HDMI_HPD_STATUS_REG 0x1BA0 +#define HDMI0_HPD_FLAG BIT(0) +#define HDMI1_HPD_FLAG BIT(1) + +#define LSDC_HDMI0_PHY_CAL_REG 0x18C0 +#define LSDC_HDMI1_PHY_CAL_REG 0x18D0 + +/* AVI InfoFrame */ +#define LSDC_HDMI0_AVI_CONTENT0 0x18E0 +#define LSDC_HDMI1_AVI_CONTENT0 0x18D0 +#define LSDC_HDMI0_AVI_CONTENT1 0x1900 +#define LSDC_HDMI1_AVI_CONTENT1 0x1910 +#define LSDC_HDMI0_AVI_CONTENT2 0x1920 +#define LSDC_HDMI1_AVI_CONTENT2 0x1930 +#define LSDC_HDMI0_AVI_CONTENT3 0x1940 +#define LSDC_HDMI1_AVI_CONTENT3 0x1950 + +/* 1: enable avi infoframe packet, 0: disable avi infoframe packet */ +#define AVI_PKT_ENABLE BIT(0) +/* 1: send one every two frame, 0: send one each frame */ +#define AVI_PKT_SEND_FREQ BIT(1) +/* + * 1: write 1 to flush avi reg content0 ~ content3 to the packet to be send, + * The hardware will clear this bit automatically. + */ +#define AVI_PKT_UPDATE BIT(2) + +#define LSDC_HDMI0_AVI_INFO_CRTL_REG 0x1960 +#define LSDC_HDMI1_AVI_INFO_CRTL_REG 0x1970 + +/* + * LS7A2000 has the hardware which count the number of vblank generated + */ +#define LSDC_CRTC0_VSYNC_COUNTER_REG 0x1A00 +#define LSDC_CRTC1_VSYNC_COUNTER_REG 0x1A10 + +/* + * LS7A2000 has the audio hardware associate with the HDMI encoder. + */ +#define LSDC_HDMI0_AUDIO_PLL_LO_REG 0x1A20 +#define LSDC_HDMI1_AUDIO_PLL_LO_REG 0x1A30 + +#define LSDC_HDMI0_AUDIO_PLL_HI_REG 0x1A40 +#define LSDC_HDMI1_AUDIO_PLL_HI_REG 0x1A50 + +#endif diff --git a/drivers/gpu/drm/loongson/lsdc_ttm.c b/drivers/gpu/drm/loongson/lsdc_ttm.c new file mode 100644 index 0000000000..bf79dc55af --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_ttm.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#include <drm/drm_drv.h> +#include <drm/drm_file.h> +#include <drm/drm_gem.h> +#include <drm/drm_managed.h> +#include <drm/drm_prime.h> + +#include "lsdc_drv.h" +#include "lsdc_ttm.h" + +const char *lsdc_mem_type_to_str(uint32_t mem_type) +{ + switch (mem_type) { + case TTM_PL_VRAM: + return "VRAM"; + case TTM_PL_TT: + return "GTT"; + case TTM_PL_SYSTEM: + return "SYSTEM"; + default: + break; + } + + return "Unknown"; +} + +const char *lsdc_domain_to_str(u32 domain) +{ + switch (domain) { + case LSDC_GEM_DOMAIN_VRAM: + return "VRAM"; + case LSDC_GEM_DOMAIN_GTT: + return "GTT"; + case LSDC_GEM_DOMAIN_SYSTEM: + return "SYSTEM"; + default: + break; + } + + return "Unknown"; +} + +static void lsdc_bo_set_placement(struct lsdc_bo *lbo, u32 domain) +{ + u32 c = 0; + u32 pflags = 0; + u32 i; + + if (lbo->tbo.base.size <= PAGE_SIZE) + pflags |= TTM_PL_FLAG_TOPDOWN; + + lbo->placement.placement = lbo->placements; + lbo->placement.busy_placement = lbo->placements; + + if (domain & LSDC_GEM_DOMAIN_VRAM) { + lbo->placements[c].mem_type = TTM_PL_VRAM; + lbo->placements[c++].flags = pflags; + } + + if (domain & LSDC_GEM_DOMAIN_GTT) { + lbo->placements[c].mem_type = TTM_PL_TT; + lbo->placements[c++].flags = pflags; + } + + if (domain & LSDC_GEM_DOMAIN_SYSTEM) { + lbo->placements[c].mem_type = TTM_PL_SYSTEM; + lbo->placements[c++].flags = 0; + } + + if (!c) { + lbo->placements[c].mem_type = TTM_PL_SYSTEM; + lbo->placements[c++].flags = 0; + } + + lbo->placement.num_placement = c; + lbo->placement.num_busy_placement = c; + + for (i = 0; i < c; ++i) { + lbo->placements[i].fpfn = 0; + lbo->placements[i].lpfn = 0; + } +} + +static void lsdc_ttm_tt_destroy(struct ttm_device *bdev, struct ttm_tt *tt) +{ + ttm_tt_fini(tt); + kfree(tt); +} + +static struct ttm_tt * +lsdc_ttm_tt_create(struct ttm_buffer_object *tbo, uint32_t page_flags) +{ + struct ttm_tt *tt; + int ret; + + tt = kzalloc(sizeof(*tt), GFP_KERNEL); + if (!tt) + return NULL; + + ret = ttm_sg_tt_init(tt, tbo, page_flags, ttm_cached); + if (ret < 0) { + kfree(tt); + return NULL; + } + + return tt; +} + +static int lsdc_ttm_tt_populate(struct ttm_device *bdev, + struct ttm_tt *ttm, + struct ttm_operation_ctx *ctx) +{ + bool slave = !!(ttm->page_flags & TTM_TT_FLAG_EXTERNAL); + + if (slave && ttm->sg) { + drm_prime_sg_to_dma_addr_array(ttm->sg, + ttm->dma_address, + ttm->num_pages); + + return 0; + } + + return ttm_pool_alloc(&bdev->pool, ttm, ctx); +} + +static void lsdc_ttm_tt_unpopulate(struct ttm_device *bdev, + struct ttm_tt *ttm) +{ + bool slave = !!(ttm->page_flags & TTM_TT_FLAG_EXTERNAL); + + if (slave) + return; + + return ttm_pool_free(&bdev->pool, ttm); +} + +static void lsdc_bo_evict_flags(struct ttm_buffer_object *tbo, + struct ttm_placement *tplacement) +{ + struct ttm_resource *resource = tbo->resource; + struct lsdc_bo *lbo = to_lsdc_bo(tbo); + + switch (resource->mem_type) { + case TTM_PL_VRAM: + lsdc_bo_set_placement(lbo, LSDC_GEM_DOMAIN_GTT); + break; + case TTM_PL_TT: + default: + lsdc_bo_set_placement(lbo, LSDC_GEM_DOMAIN_SYSTEM); + break; + } + + *tplacement = lbo->placement; +} + +static int lsdc_bo_move(struct ttm_buffer_object *tbo, + bool evict, + struct ttm_operation_ctx *ctx, + struct ttm_resource *new_mem, + struct ttm_place *hop) +{ + struct drm_device *ddev = tbo->base.dev; + struct ttm_resource *old_mem = tbo->resource; + struct lsdc_bo *lbo = to_lsdc_bo(tbo); + int ret; + + if (unlikely(tbo->pin_count > 0)) { + drm_warn(ddev, "Can't move a pinned BO\n"); + return -EINVAL; + } + + ret = ttm_bo_wait_ctx(tbo, ctx); + if (ret) + return ret; + + if (!old_mem) { + drm_dbg(ddev, "bo[%p] move: NULL to %s, size: %zu\n", + lbo, lsdc_mem_type_to_str(new_mem->mem_type), + lsdc_bo_size(lbo)); + ttm_bo_move_null(tbo, new_mem); + return 0; + } + + if (old_mem->mem_type == TTM_PL_SYSTEM && !tbo->ttm) { + ttm_bo_move_null(tbo, new_mem); + drm_dbg(ddev, "bo[%p] move: SYSTEM to NULL, size: %zu\n", + lbo, lsdc_bo_size(lbo)); + return 0; + } + + if (old_mem->mem_type == TTM_PL_SYSTEM && + new_mem->mem_type == TTM_PL_TT) { + drm_dbg(ddev, "bo[%p] move: SYSTEM to GTT, size: %zu\n", + lbo, lsdc_bo_size(lbo)); + ttm_bo_move_null(tbo, new_mem); + return 0; + } + + if (old_mem->mem_type == TTM_PL_TT && + new_mem->mem_type == TTM_PL_SYSTEM) { + drm_dbg(ddev, "bo[%p] move: GTT to SYSTEM, size: %zu\n", + lbo, lsdc_bo_size(lbo)); + ttm_resource_free(tbo, &tbo->resource); + ttm_bo_assign_mem(tbo, new_mem); + return 0; + } + + drm_dbg(ddev, "bo[%p] move: %s to %s, size: %zu\n", + lbo, + lsdc_mem_type_to_str(old_mem->mem_type), + lsdc_mem_type_to_str(new_mem->mem_type), + lsdc_bo_size(lbo)); + + return ttm_bo_move_memcpy(tbo, ctx, new_mem); +} + +static int lsdc_bo_reserve_io_mem(struct ttm_device *bdev, + struct ttm_resource *mem) +{ + struct lsdc_device *ldev = tdev_to_ldev(bdev); + + switch (mem->mem_type) { + case TTM_PL_SYSTEM: + break; + case TTM_PL_TT: + break; + case TTM_PL_VRAM: + mem->bus.offset = (mem->start << PAGE_SHIFT) + ldev->vram_base; + mem->bus.is_iomem = true; + mem->bus.caching = ttm_write_combined; + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct ttm_device_funcs lsdc_bo_driver = { + .ttm_tt_create = lsdc_ttm_tt_create, + .ttm_tt_populate = lsdc_ttm_tt_populate, + .ttm_tt_unpopulate = lsdc_ttm_tt_unpopulate, + .ttm_tt_destroy = lsdc_ttm_tt_destroy, + .eviction_valuable = ttm_bo_eviction_valuable, + .evict_flags = lsdc_bo_evict_flags, + .move = lsdc_bo_move, + .io_mem_reserve = lsdc_bo_reserve_io_mem, +}; + +u64 lsdc_bo_gpu_offset(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + struct drm_device *ddev = tbo->base.dev; + struct ttm_resource *resource = tbo->resource; + + if (unlikely(!tbo->pin_count)) { + drm_err(ddev, "unpinned bo, gpu virtual address is invalid\n"); + return 0; + } + + if (unlikely(resource->mem_type == TTM_PL_SYSTEM)) + return 0; + + return resource->start << PAGE_SHIFT; +} + +size_t lsdc_bo_size(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + + return tbo->base.size; +} + +int lsdc_bo_reserve(struct lsdc_bo *lbo) +{ + return ttm_bo_reserve(&lbo->tbo, true, false, NULL); +} + +void lsdc_bo_unreserve(struct lsdc_bo *lbo) +{ + return ttm_bo_unreserve(&lbo->tbo); +} + +int lsdc_bo_pin(struct lsdc_bo *lbo, u32 domain, u64 *gpu_addr) +{ + struct ttm_operation_ctx ctx = { false, false }; + struct ttm_buffer_object *tbo = &lbo->tbo; + struct lsdc_device *ldev = tdev_to_ldev(tbo->bdev); + int ret; + + if (tbo->pin_count) + goto bo_pinned; + + if (lbo->sharing_count && domain == LSDC_GEM_DOMAIN_VRAM) + return -EINVAL; + + if (domain) + lsdc_bo_set_placement(lbo, domain); + + ret = ttm_bo_validate(tbo, &lbo->placement, &ctx); + if (unlikely(ret)) { + drm_err(&ldev->base, "%p validate failed: %d\n", lbo, ret); + return ret; + } + + if (domain == LSDC_GEM_DOMAIN_VRAM) + ldev->vram_pinned_size += lsdc_bo_size(lbo); + else if (domain == LSDC_GEM_DOMAIN_GTT) + ldev->gtt_pinned_size += lsdc_bo_size(lbo); + +bo_pinned: + ttm_bo_pin(tbo); + + if (gpu_addr) + *gpu_addr = lsdc_bo_gpu_offset(lbo); + + return 0; +} + +void lsdc_bo_unpin(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + struct lsdc_device *ldev = tdev_to_ldev(tbo->bdev); + + if (unlikely(!tbo->pin_count)) { + drm_dbg(&ldev->base, "%p unpin is not necessary\n", lbo); + return; + } + + ttm_bo_unpin(tbo); + + if (!tbo->pin_count) { + if (tbo->resource->mem_type == TTM_PL_VRAM) + ldev->vram_pinned_size -= lsdc_bo_size(lbo); + else if (tbo->resource->mem_type == TTM_PL_TT) + ldev->gtt_pinned_size -= lsdc_bo_size(lbo); + } +} + +void lsdc_bo_ref(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + + ttm_bo_get(tbo); +} + +void lsdc_bo_unref(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + + ttm_bo_put(tbo); +} + +int lsdc_bo_kmap(struct lsdc_bo *lbo) +{ + struct ttm_buffer_object *tbo = &lbo->tbo; + struct drm_gem_object *gem = &tbo->base; + struct drm_device *ddev = gem->dev; + long ret; + int err; + + ret = dma_resv_wait_timeout(gem->resv, DMA_RESV_USAGE_KERNEL, false, + MAX_SCHEDULE_TIMEOUT); + if (ret < 0) { + drm_warn(ddev, "wait fence timeout\n"); + return ret; + } + + if (lbo->kptr) + return 0; + + err = ttm_bo_kmap(tbo, 0, PFN_UP(lsdc_bo_size(lbo)), &lbo->kmap); + if (err) { + drm_err(ddev, "kmap %p failed: %d\n", lbo, err); + return err; + } + + lbo->kptr = ttm_kmap_obj_virtual(&lbo->kmap, &lbo->is_iomem); + + return 0; +} + +void lsdc_bo_kunmap(struct lsdc_bo *lbo) +{ + if (!lbo->kptr) + return; + + lbo->kptr = NULL; + ttm_bo_kunmap(&lbo->kmap); +} + +void lsdc_bo_clear(struct lsdc_bo *lbo) +{ + lsdc_bo_kmap(lbo); + + if (lbo->is_iomem) + memset_io((void __iomem *)lbo->kptr, 0, lbo->size); + else + memset(lbo->kptr, 0, lbo->size); + + lsdc_bo_kunmap(lbo); +} + +int lsdc_bo_evict_vram(struct drm_device *ddev) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + struct ttm_device *bdev = &ldev->bdev; + struct ttm_resource_manager *man; + + man = ttm_manager_type(bdev, TTM_PL_VRAM); + if (unlikely(!man)) + return 0; + + return ttm_resource_manager_evict_all(bdev, man); +} + +static void lsdc_bo_destroy(struct ttm_buffer_object *tbo) +{ + struct lsdc_device *ldev = tdev_to_ldev(tbo->bdev); + struct lsdc_bo *lbo = to_lsdc_bo(tbo); + + mutex_lock(&ldev->gem.mutex); + list_del_init(&lbo->list); + mutex_unlock(&ldev->gem.mutex); + + drm_gem_object_release(&tbo->base); + + kfree(lbo); +} + +struct lsdc_bo *lsdc_bo_create(struct drm_device *ddev, + u32 domain, + size_t size, + bool kernel, + struct sg_table *sg, + struct dma_resv *resv) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + struct ttm_device *bdev = &ldev->bdev; + struct ttm_buffer_object *tbo; + struct lsdc_bo *lbo; + enum ttm_bo_type bo_type; + int ret; + + lbo = kzalloc(sizeof(*lbo), GFP_KERNEL); + if (!lbo) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&lbo->list); + + lbo->initial_domain = domain & (LSDC_GEM_DOMAIN_VRAM | + LSDC_GEM_DOMAIN_GTT | + LSDC_GEM_DOMAIN_SYSTEM); + + tbo = &lbo->tbo; + + size = ALIGN(size, PAGE_SIZE); + + ret = drm_gem_object_init(ddev, &tbo->base, size); + if (ret) { + kfree(lbo); + return ERR_PTR(ret); + } + + tbo->bdev = bdev; + + if (kernel) + bo_type = ttm_bo_type_kernel; + else if (sg) + bo_type = ttm_bo_type_sg; + else + bo_type = ttm_bo_type_device; + + lsdc_bo_set_placement(lbo, domain); + lbo->size = size; + + ret = ttm_bo_init_validate(bdev, tbo, bo_type, &lbo->placement, 0, + false, sg, resv, lsdc_bo_destroy); + if (ret) { + kfree(lbo); + return ERR_PTR(ret); + } + + return lbo; +} + +struct lsdc_bo *lsdc_bo_create_kernel_pinned(struct drm_device *ddev, + u32 domain, + size_t size) +{ + struct lsdc_bo *lbo; + int ret; + + lbo = lsdc_bo_create(ddev, domain, size, true, NULL, NULL); + if (IS_ERR(lbo)) + return ERR_CAST(lbo); + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) { + lsdc_bo_unref(lbo); + return ERR_PTR(ret); + } + + ret = lsdc_bo_pin(lbo, domain, NULL); + lsdc_bo_unreserve(lbo); + if (unlikely(ret)) { + lsdc_bo_unref(lbo); + return ERR_PTR(ret); + } + + return lbo; +} + +void lsdc_bo_free_kernel_pinned(struct lsdc_bo *lbo) +{ + int ret; + + ret = lsdc_bo_reserve(lbo); + if (unlikely(ret)) + return; + + lsdc_bo_unpin(lbo); + lsdc_bo_unreserve(lbo); + + lsdc_bo_unref(lbo); +} + +static void lsdc_ttm_fini(struct drm_device *ddev, void *data) +{ + struct lsdc_device *ldev = (struct lsdc_device *)data; + + ttm_range_man_fini(&ldev->bdev, TTM_PL_VRAM); + ttm_range_man_fini(&ldev->bdev, TTM_PL_TT); + + ttm_device_fini(&ldev->bdev); + + drm_dbg(ddev, "ttm finished\n"); +} + +int lsdc_ttm_init(struct lsdc_device *ldev) +{ + struct drm_device *ddev = &ldev->base; + unsigned long num_vram_pages; + unsigned long num_gtt_pages; + int ret; + + ret = ttm_device_init(&ldev->bdev, &lsdc_bo_driver, ddev->dev, + ddev->anon_inode->i_mapping, + ddev->vma_offset_manager, false, true); + if (ret) + return ret; + + num_vram_pages = ldev->vram_size >> PAGE_SHIFT; + + ret = ttm_range_man_init(&ldev->bdev, TTM_PL_VRAM, false, num_vram_pages); + if (unlikely(ret)) + return ret; + + drm_info(ddev, "VRAM: %lu pages ready\n", num_vram_pages); + + /* 512M is far enough for us now */ + ldev->gtt_size = 512 << 20; + + num_gtt_pages = ldev->gtt_size >> PAGE_SHIFT; + + ret = ttm_range_man_init(&ldev->bdev, TTM_PL_TT, true, num_gtt_pages); + if (unlikely(ret)) + return ret; + + drm_info(ddev, "GTT: %lu pages ready\n", num_gtt_pages); + + return drmm_add_action_or_reset(ddev, lsdc_ttm_fini, ldev); +} + +void lsdc_ttm_debugfs_init(struct lsdc_device *ldev) +{ + struct ttm_device *bdev = &ldev->bdev; + struct drm_device *ddev = &ldev->base; + struct drm_minor *minor = ddev->primary; + struct dentry *root = minor->debugfs_root; + struct ttm_resource_manager *vram_man; + struct ttm_resource_manager *gtt_man; + + vram_man = ttm_manager_type(bdev, TTM_PL_VRAM); + gtt_man = ttm_manager_type(bdev, TTM_PL_TT); + + ttm_resource_manager_create_debugfs(vram_man, root, "vram_mm"); + ttm_resource_manager_create_debugfs(gtt_man, root, "gtt_mm"); +} diff --git a/drivers/gpu/drm/loongson/lsdc_ttm.h b/drivers/gpu/drm/loongson/lsdc_ttm.h new file mode 100644 index 0000000000..843e147506 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_ttm.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Loongson Technology Corporation Limited + */ + +#ifndef __LSDC_TTM_H__ +#define __LSDC_TTM_H__ + +#include <linux/container_of.h> +#include <linux/iosys-map.h> +#include <linux/list.h> + +#include <drm/drm_gem.h> +#include <drm/ttm/ttm_bo.h> +#include <drm/ttm/ttm_placement.h> +#include <drm/ttm/ttm_range_manager.h> +#include <drm/ttm/ttm_tt.h> + +#define LSDC_GEM_DOMAIN_SYSTEM 0x1 +#define LSDC_GEM_DOMAIN_GTT 0x2 +#define LSDC_GEM_DOMAIN_VRAM 0x4 + +struct lsdc_bo { + struct ttm_buffer_object tbo; + + /* Protected by gem.mutex */ + struct list_head list; + + struct iosys_map map; + + unsigned int vmap_count; + /* cross device driver sharing reference count */ + unsigned int sharing_count; + + struct ttm_bo_kmap_obj kmap; + void *kptr; + bool is_iomem; + + size_t size; + + u32 initial_domain; + + struct ttm_placement placement; + struct ttm_place placements[4]; +}; + +static inline struct ttm_buffer_object *to_ttm_bo(struct drm_gem_object *gem) +{ + return container_of(gem, struct ttm_buffer_object, base); +} + +static inline struct lsdc_bo *to_lsdc_bo(struct ttm_buffer_object *tbo) +{ + return container_of(tbo, struct lsdc_bo, tbo); +} + +static inline struct lsdc_bo *gem_to_lsdc_bo(struct drm_gem_object *gem) +{ + return container_of(gem, struct lsdc_bo, tbo.base); +} + +const char *lsdc_mem_type_to_str(uint32_t mem_type); +const char *lsdc_domain_to_str(u32 domain); + +struct lsdc_bo *lsdc_bo_create(struct drm_device *ddev, + u32 domain, + size_t size, + bool kernel, + struct sg_table *sg, + struct dma_resv *resv); + +struct lsdc_bo *lsdc_bo_create_kernel_pinned(struct drm_device *ddev, + u32 domain, + size_t size); + +void lsdc_bo_free_kernel_pinned(struct lsdc_bo *lbo); + +int lsdc_bo_reserve(struct lsdc_bo *lbo); +void lsdc_bo_unreserve(struct lsdc_bo *lbo); + +int lsdc_bo_pin(struct lsdc_bo *lbo, u32 domain, u64 *gpu_addr); +void lsdc_bo_unpin(struct lsdc_bo *lbo); + +void lsdc_bo_ref(struct lsdc_bo *lbo); +void lsdc_bo_unref(struct lsdc_bo *lbo); + +u64 lsdc_bo_gpu_offset(struct lsdc_bo *lbo); +size_t lsdc_bo_size(struct lsdc_bo *lbo); + +int lsdc_bo_kmap(struct lsdc_bo *lbo); +void lsdc_bo_kunmap(struct lsdc_bo *lbo); +void lsdc_bo_clear(struct lsdc_bo *lbo); + +int lsdc_bo_evict_vram(struct drm_device *ddev); + +int lsdc_ttm_init(struct lsdc_device *ldev); +void lsdc_ttm_debugfs_init(struct lsdc_device *ldev); + +#endif |