mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-28 22:17:12 +08:00
308 lines
6.7 KiB
C
308 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* KMS driver for Loongson display controller
|
|
* Copyright (C) 2022 Loongson Corporation
|
|
*/
|
|
|
|
/*
|
|
* Authors:
|
|
* Sui Jingfeng <suijingfeng@loongson.cn>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/console.h>
|
|
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
|
|
#include "lsdc_drv.h"
|
|
#include "lsdc_regs.h"
|
|
|
|
static const struct lsdc_chip_desc dc_in_ls7a1000 = {
|
|
.chip = LSDC_CHIP_7A1000,
|
|
.num_of_crtc = LSDC_NUM_CRTC,
|
|
.max_pixel_clk = 200000,
|
|
.max_width = 2048,
|
|
.max_height = 2048,
|
|
.num_of_hw_cursor = 1,
|
|
.hw_cursor_w = 32,
|
|
.hw_cursor_h = 32,
|
|
.stride_alignment = 256,
|
|
.has_builtin_i2c = true,
|
|
.has_vram = true,
|
|
.broken_gamma = true,
|
|
};
|
|
|
|
static const struct lsdc_chip_desc dc_in_ls7a2000 = {
|
|
.chip = LSDC_CHIP_7A2000,
|
|
.num_of_crtc = LSDC_NUM_CRTC,
|
|
.max_pixel_clk = 200000,
|
|
.max_width = 2048,
|
|
.max_height = 2048,
|
|
.num_of_hw_cursor = 2,
|
|
.hw_cursor_w = 64,
|
|
.hw_cursor_h = 64,
|
|
.stride_alignment = 256,
|
|
.has_builtin_i2c = true,
|
|
.has_vram = true,
|
|
.broken_gamma = true,
|
|
};
|
|
|
|
static int lsdc_drm_suspend(struct drm_device *ddev, bool suspend)
|
|
{
|
|
drm_mode_config_helper_suspend(ddev);
|
|
|
|
if (suspend && ddev->pdev) {
|
|
pci_save_state(ddev->pdev);
|
|
pci_set_power_state(ddev->pdev, PCI_D3hot);
|
|
/* Shut down the device */
|
|
pci_disable_device(ddev->pdev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_drm_resume(struct drm_device *ddev, bool resume)
|
|
{
|
|
if (resume && ddev->pdev) {
|
|
pci_set_power_state(ddev->pdev, PCI_D0);
|
|
pci_restore_state(ddev->pdev);
|
|
|
|
if (pci_enable_device(ddev->pdev))
|
|
return -EIO;
|
|
}
|
|
|
|
drm_mode_config_helper_resume(ddev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_drm_pm_suspend(struct device *dev)
|
|
{
|
|
struct lsdc_device *ldev = dev_get_drvdata(dev);
|
|
|
|
return lsdc_drm_suspend(ldev->ddev, true);
|
|
}
|
|
|
|
static int lsdc_drm_pm_resume(struct device *dev)
|
|
{
|
|
struct lsdc_device *ldev = dev_get_drvdata(dev);
|
|
|
|
return lsdc_drm_resume(ldev->ddev, true);
|
|
}
|
|
|
|
static int lsdc_drm_pm_freeze(struct device *dev)
|
|
{
|
|
struct lsdc_device *ldev = dev_get_drvdata(dev);
|
|
|
|
return lsdc_drm_suspend(ldev->ddev, false);
|
|
}
|
|
|
|
static int lsdc_drm_pm_thaw(struct device *dev)
|
|
{
|
|
struct lsdc_device *ldev = dev_get_drvdata(dev);
|
|
|
|
return lsdc_drm_resume(ldev->ddev, false);
|
|
}
|
|
|
|
static int lsdc_vram_init(struct lsdc_device *ldev)
|
|
{
|
|
const struct lsdc_chip_desc * const descp = ldev->desc;
|
|
struct pci_dev *gpu;
|
|
resource_size_t base, size;
|
|
|
|
if (descp->chip == LSDC_CHIP_7A2000) {
|
|
/* BAR 2 of LS7A2000's GPU contain VRAM */
|
|
gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL);
|
|
} else if (descp->chip == LSDC_CHIP_7A1000) {
|
|
/* BAR 2 of LS7A1000's GPU(GC1000) contain VRAM */
|
|
gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL);
|
|
} else {
|
|
dev_err(ldev->dev, "Unknown chip, the driver need update\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (IS_ERR_OR_NULL(gpu)) {
|
|
dev_err(ldev->dev, "Can not get VRAM\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
base = pci_resource_start(gpu, 2);
|
|
size = pci_resource_len(gpu, 2);
|
|
|
|
ldev->vram_base = base;
|
|
ldev->vram_size = size;
|
|
|
|
dev_info(ldev->dev, "vram start: 0x%llx, size: %uMB\n",
|
|
(u64)base, (u32)(size >> 20));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lsdc_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id * const ent)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct lsdc_device *ldev;
|
|
const struct lsdc_chip_desc *desc;
|
|
struct resource *bar0;
|
|
int ret;
|
|
u8 chip_revision;
|
|
|
|
ret = pcim_enable_device(pdev);
|
|
if (ret) {
|
|
DRM_ERROR("Enable pci devive failed.\n");
|
|
return ret;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
|
|
ldev = kzalloc(sizeof(struct lsdc_device), GFP_KERNEL);
|
|
if (ldev == NULL)
|
|
return -ENOMEM;
|
|
|
|
desc = (struct lsdc_chip_desc *)ent->driver_data;
|
|
|
|
ldev->desc = desc;
|
|
ldev->dev = dev;
|
|
|
|
if (desc == NULL) {
|
|
DRM_ERROR("unknown dc chip core\n");
|
|
ret = -ENOENT;
|
|
goto err_free;
|
|
}
|
|
|
|
ldev->irq = pdev->irq;
|
|
|
|
/* BAR 0 contains registers */
|
|
bar0 = devm_request_mem_region(dev,
|
|
pci_resource_start(pdev, 0),
|
|
pci_resource_len(pdev, 0),
|
|
dev_name(dev));
|
|
|
|
if (bar0 == NULL) {
|
|
DRM_ERROR("Can't reserve mmio registers\n");
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
ldev->reg_base = pci_iomap_range(pdev, 0, 0, 0);
|
|
if (ldev->reg_base == NULL) {
|
|
DRM_ERROR("Unable to get lsdc registers\n");
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
ret = lsdc_vram_init(ldev);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
/* Get the revison of this DC */
|
|
pci_read_config_byte(pdev, 0x8, &chip_revision);
|
|
DRM_INFO("DC revision is %d\n", chip_revision);
|
|
|
|
if (!request_mem_region(ldev->vram_base, ldev->vram_size,
|
|
"lsdc_drmfb_vram")) {
|
|
DRM_ERROR("can't reserve VRAM\n");
|
|
ret = -ENXIO;
|
|
goto err_free;
|
|
}
|
|
|
|
DRM_INFO("vram start: 0x%llx, size: %lluMB\n",
|
|
ldev->vram_base, ldev->vram_size >> 20);
|
|
|
|
ldev->vram = devm_ioremap_wc(dev, ldev->vram_base, ldev->vram_size);
|
|
if (ldev->vram == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err_free;
|
|
}
|
|
|
|
DRM_INFO("vram virtual addr: 0x%llx\n", (u64)ldev->vram);
|
|
|
|
ldev->ddev->pdev = pdev;
|
|
|
|
dev_set_drvdata(dev, ldev);
|
|
|
|
ret = lsdc_common_probe(ldev);
|
|
if (ret)
|
|
goto err_free;
|
|
|
|
return ret;
|
|
|
|
err_free:
|
|
kfree(ldev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void lsdc_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct lsdc_device *ldev = dev_get_drvdata(&pdev->dev);
|
|
|
|
if (ldev && ldev->vram) {
|
|
devm_iounmap(&pdev->dev, ldev->vram);
|
|
ldev->vram = NULL;
|
|
}
|
|
|
|
lsdc_common_remove(ldev);
|
|
pci_clear_master(pdev);
|
|
pci_release_regions(pdev);
|
|
}
|
|
|
|
const struct dev_pm_ops lsdc_drm_pm_ops = {
|
|
.suspend = lsdc_drm_pm_suspend,
|
|
.resume = lsdc_drm_pm_resume,
|
|
.freeze = lsdc_drm_pm_freeze,
|
|
.thaw = lsdc_drm_pm_thaw,
|
|
.poweroff = lsdc_drm_pm_freeze,
|
|
.restore = lsdc_drm_pm_resume,
|
|
};
|
|
|
|
static const struct pci_device_id lsdc_pciid_list[] = {
|
|
{PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&dc_in_ls7a1000},
|
|
{PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&dc_in_ls7a2000},
|
|
{0, 0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
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_drm_pm_ops,
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, lsdc_pciid_list);
|
|
|
|
static int __init lsdc_pci_init(void)
|
|
{
|
|
struct pci_dev *pdev = NULL;
|
|
|
|
/* Multi video card workaround */
|
|
while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
|
|
if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) {
|
|
DRM_INFO("Discrete graphic card detected, abort.\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (vgacon_text_force()) {
|
|
DRM_ERROR("VGACON disables loongson kernel modesetting.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return pci_register_driver(&lsdc_pci_driver);
|
|
}
|
|
module_init(lsdc_pci_init);
|
|
|
|
static void __exit lsdc_pci_exit(void)
|
|
{
|
|
return pci_unregister_driver(&lsdc_pci_driver);
|
|
}
|
|
module_exit(lsdc_pci_exit);
|
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
|
MODULE_LICENSE("GPL v2"); |