Files
2024-06-17 20:18:01 +08:00

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");