438 lines
11 KiB
C
438 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Phytium display drm driver
|
|
*
|
|
* Copyright (C) 2021-2023, Phytium Technology Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/pci.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_fb_helper.h>
|
|
#include <linux/dmaengine.h>
|
|
#include "phytium_display_drv.h"
|
|
#include "phytium_pci.h"
|
|
#include "phytium_dp.h"
|
|
#include "phytium_gem.h"
|
|
#include "px210_dc.h"
|
|
#include "px210_dp.h"
|
|
#include "pe220x_dc.h"
|
|
#include "pe220x_dp.h"
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/module.h>
|
|
int dc_msi_enable;
|
|
module_param(dc_msi_enable, int, 0644);
|
|
MODULE_PARM_DESC(dc_msi_enable, "Enable DC msi interrupt (0-disabled; 1-enabled; default-0)");
|
|
|
|
void phytium_pci_vram_hw_init(struct phytium_display_private *priv)
|
|
{
|
|
struct phytium_pci_private *pci_priv = to_pci_priv(priv);
|
|
|
|
pci_priv->dc_hw_vram_init(priv, priv->pool_phys_addr, priv->pool_size);
|
|
}
|
|
|
|
static bool phytium_pci_host_is_5c01(struct pci_bus *bus)
|
|
{
|
|
struct pci_bus *child = bus;
|
|
struct pci_dev *root = NULL;
|
|
|
|
while (child) {
|
|
if (child->parent->parent)
|
|
child = child->parent;
|
|
else
|
|
break;
|
|
}
|
|
|
|
root = child->self;
|
|
if ((root->vendor == 0x1db7) && (root->device == 0x5c01))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int phytium_pci_vram_init(struct pci_dev *pdev, struct phytium_display_private *priv)
|
|
{
|
|
int ret = 0;
|
|
|
|
priv->pool_phys_addr = pci_resource_start(pdev, 2);
|
|
priv->pool_size = pci_resource_len(pdev, 2);
|
|
if ((priv->pool_phys_addr != 0) && (priv->pool_size != 0)) {
|
|
if ((pdev->device == 0xdc3e) && phytium_pci_host_is_5c01(pdev->bus)) {
|
|
priv->pool_virt_addr = devm_ioremap(&pdev->dev, priv->pool_phys_addr,
|
|
priv->pool_size);
|
|
priv->support_memory_type = MEMORY_TYPE_VRAM_DEVICE;
|
|
} else {
|
|
priv->pool_virt_addr = devm_ioremap_wc(&pdev->dev, priv->pool_phys_addr,
|
|
priv->pool_size);
|
|
priv->support_memory_type = MEMORY_TYPE_VRAM_WC;
|
|
}
|
|
if (priv->pool_virt_addr == NULL) {
|
|
DRM_ERROR("pci vram ioremap fail, addr:0x%llx, size:0x%llx\n",
|
|
priv->pool_phys_addr, priv->pool_size);
|
|
ret = -EINVAL;
|
|
goto failed_ioremap;
|
|
}
|
|
ret = phytium_memory_pool_init(&pdev->dev, priv);
|
|
if (ret)
|
|
goto failed_init_memory_pool;
|
|
|
|
priv->mem_state[PHYTIUM_MEM_VRAM_TOTAL] = priv->pool_size;
|
|
priv->vram_hw_init = phytium_pci_vram_hw_init;
|
|
} else {
|
|
DRM_DEBUG_KMS("not support vram\n");
|
|
priv->pool_virt_addr = NULL;
|
|
priv->mem_state[PHYTIUM_MEM_VRAM_TOTAL] = 0;
|
|
priv->support_memory_type = MEMORY_TYPE_SYSTEM_UNIFIED;
|
|
priv->vram_hw_init = NULL;
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed_init_memory_pool:
|
|
devm_iounmap(&pdev->dev, priv->pool_virt_addr);
|
|
failed_ioremap:
|
|
return ret;
|
|
}
|
|
|
|
void phytium_pci_vram_fini(struct pci_dev *pdev, struct phytium_display_private *priv)
|
|
{
|
|
if ((priv->support_memory_type == MEMORY_TYPE_VRAM_WC) ||
|
|
(priv->support_memory_type == MEMORY_TYPE_VRAM_DEVICE)) {
|
|
phytium_memory_pool_fini(&pdev->dev, priv);
|
|
devm_iounmap(&pdev->dev, priv->pool_virt_addr);
|
|
}
|
|
}
|
|
|
|
static bool phytium_pci_dma_chan_filter(struct dma_chan *chan, void *param)
|
|
{
|
|
struct phytium_dma_slave *s = param;
|
|
|
|
if (s->dma_dev != chan->device->dev)
|
|
return false;
|
|
|
|
if (s->chan_id == chan->chan_id)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
int phytium_pci_dma_init(struct phytium_display_private *priv)
|
|
{
|
|
struct pci_dev *dma_dev, *gpu_dev;
|
|
struct drm_device *drm_dev = priv->dev;
|
|
dma_cap_mask_t mask;
|
|
struct phytium_dma_slave s;
|
|
int ret = 0;
|
|
u16 cmd;
|
|
|
|
/* check px210 gpu enable */
|
|
gpu_dev = pci_get_device(PCI_VENDOR_ID_PHYTIUM, 0xdc20, NULL);
|
|
if (!gpu_dev) {
|
|
DRM_INFO("failed to get gpu_dev\n");
|
|
ret = -ENODEV;
|
|
goto failed;
|
|
}
|
|
|
|
pci_read_config_word(gpu_dev, PCI_COMMAND, &cmd);
|
|
if (!(cmd & PCI_COMMAND_MASTER)) {
|
|
DRM_INFO("gpu_dev master is disabled\n");
|
|
ret = -ENODEV;
|
|
goto failed;
|
|
}
|
|
|
|
dma_dev = pci_get_device(PCI_VENDOR_ID_PHYTIUM, 0xdc3c, NULL);
|
|
if (!dma_dev) {
|
|
DRM_INFO("failed to get dma_dev\n");
|
|
ret = -ENODEV;
|
|
goto failed;
|
|
}
|
|
|
|
dma_cap_zero(mask);
|
|
dma_cap_set(DMA_SLAVE, mask);
|
|
|
|
s.dma_dev = &dma_dev->dev;
|
|
s.chan_id = 2;
|
|
priv->dma_chan = dma_request_channel(mask, phytium_pci_dma_chan_filter, &s);
|
|
if (!priv->dma_chan) {
|
|
DRM_DEV_ERROR(drm_dev->dev, "failed to request dma chan\n");
|
|
ret = -EBUSY;
|
|
goto failed;
|
|
}
|
|
priv->dma_inited = 1;
|
|
|
|
failed:
|
|
return ret;
|
|
}
|
|
|
|
void phytium_pci_dma_fini(struct phytium_display_private *priv)
|
|
{
|
|
if (priv->dma_inited)
|
|
dma_release_channel(priv->dma_chan);
|
|
priv->dma_inited = 0;
|
|
priv->dma_chan = NULL;
|
|
}
|
|
|
|
static struct phytium_display_private*
|
|
phytium_pci_private_init(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
struct drm_device *dev = pci_get_drvdata(pdev);
|
|
struct phytium_display_private *priv = NULL;
|
|
struct phytium_pci_private *pci_priv = NULL;
|
|
struct phytium_device_info *phytium_info = (struct phytium_device_info *)ent->driver_data;
|
|
int i = 0;
|
|
resource_size_t io_addr, io_size;
|
|
|
|
pci_priv = devm_kzalloc(&pdev->dev, sizeof(*pci_priv), GFP_KERNEL);
|
|
if (!pci_priv) {
|
|
DRM_ERROR("no memory to allocate for drm_display_private\n");
|
|
goto failed_malloc_priv;
|
|
}
|
|
|
|
memset(pci_priv, 0, sizeof(*pci_priv));
|
|
priv = &pci_priv->base;
|
|
phytium_display_private_init(priv, dev);
|
|
|
|
memcpy(&(priv->info), phytium_info, sizeof(struct phytium_device_info));
|
|
DRM_DEBUG_KMS("priv->info.num_pipes :%d\n", priv->info.num_pipes);
|
|
priv->info.pipe_mask = ((pdev->subsystem_device >> PIPE_MASK_SHIFT) & PIPE_MASK_MASK);
|
|
priv->info.edp_mask = ((pdev->subsystem_device >> EDP_MASK_SHIFT) & EDP_MASK_MASK);
|
|
priv->info.num_pipes = 0;
|
|
for_each_pipe_masked(priv, i)
|
|
priv->info.num_pipes++;
|
|
if (priv->info.num_pipes == 0) {
|
|
DRM_ERROR("num_pipes is zero, so exit init\n");
|
|
goto failed_init_numpipe;
|
|
}
|
|
|
|
io_addr = pci_resource_start(pdev, 0);
|
|
io_size = pci_resource_len(pdev, 0);
|
|
priv->regs = ioremap(io_addr, io_size);
|
|
if (priv->regs == NULL) {
|
|
DRM_ERROR("pci bar0 ioremap fail, addr:0x%llx, size:0x%llx\n", io_addr, io_size);
|
|
goto failed_ioremap;
|
|
}
|
|
|
|
priv->irq = pdev->irq;
|
|
if (IS_PX210(priv)) {
|
|
pci_priv->dc_hw_vram_init = px210_dc_hw_vram_init;
|
|
priv->dc_hw_clear_msi_irq = px210_dc_hw_clear_msi_irq;
|
|
priv->dc_hw_fb_format_check = px210_dc_hw_fb_format_check;
|
|
} else if (IS_PE220X(priv)) {
|
|
pci_priv->dc_hw_vram_init = pe220x_dc_hw_vram_init;
|
|
priv->dc_hw_clear_msi_irq = NULL;
|
|
priv->dc_hw_fb_format_check = pe220x_dc_hw_fb_format_check;
|
|
}
|
|
|
|
return priv;
|
|
|
|
failed_ioremap:
|
|
failed_init_numpipe:
|
|
devm_kfree(&pdev->dev, pci_priv);
|
|
failed_malloc_priv:
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
phytium_pci_private_fini(struct pci_dev *pdev, struct phytium_display_private *priv)
|
|
{
|
|
struct phytium_pci_private *pci_priv = to_pci_priv(priv);
|
|
|
|
if (priv->regs)
|
|
iounmap(priv->regs);
|
|
|
|
devm_kfree(&pdev->dev, pci_priv);
|
|
}
|
|
|
|
static int phytium_kick_out_firmware_fb(struct pci_dev *pdev)
|
|
{
|
|
struct apertures_struct *ap;
|
|
|
|
ap = alloc_apertures(1);
|
|
if (!ap)
|
|
return -ENOMEM;
|
|
|
|
ap->ranges[0].base = pci_resource_start(pdev, 2);
|
|
ap->ranges[0].size = pci_resource_len(pdev, 2);
|
|
|
|
drm_fb_helper_remove_conflicting_framebuffers(ap, "phytiumdrmfb",
|
|
false);
|
|
kfree(ap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int phytium_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
struct phytium_display_private *priv = NULL;
|
|
struct drm_device *dev = NULL;
|
|
int ret = 0;
|
|
|
|
ret = phytium_kick_out_firmware_fb(pdev);
|
|
if (ret)
|
|
DRM_ERROR("failed to remove conflicting framebuffers\n");
|
|
|
|
dev = drm_dev_alloc(&phytium_display_drm_driver, &pdev->dev);
|
|
if (IS_ERR(dev)) {
|
|
DRM_ERROR("failed to allocate drm_device\n");
|
|
return PTR_ERR(dev);
|
|
}
|
|
dev->pdev = pdev;
|
|
pci_set_drvdata(pdev, dev);
|
|
pci_set_master(pdev);
|
|
ret = pci_enable_device(pdev);
|
|
if (ret) {
|
|
DRM_ERROR("pci enbale device fail\n");
|
|
goto failed_enable_device;
|
|
}
|
|
|
|
if (dc_msi_enable) {
|
|
ret = pci_enable_msi(pdev);
|
|
if (ret)
|
|
DRM_ERROR("pci enbale msi fail\n");
|
|
}
|
|
|
|
dma_set_mask(&pdev->dev, DMA_BIT_MASK(40));
|
|
|
|
priv = phytium_pci_private_init(pdev, ent);
|
|
if (priv)
|
|
dev->dev_private = priv;
|
|
else
|
|
goto failed_pci_private_init;
|
|
|
|
ret = phytium_pci_vram_init(pdev, priv);
|
|
if (ret) {
|
|
DRM_ERROR("failed to init pci vram\n");
|
|
goto failed_pci_vram_init;
|
|
}
|
|
|
|
ret = drm_dev_register(dev, 0);
|
|
if (ret) {
|
|
DRM_ERROR("failed to register drm dev\n");
|
|
goto failed_register_drm;
|
|
}
|
|
|
|
phytium_dp_hpd_irq_setup(dev, true);
|
|
|
|
return 0;
|
|
|
|
failed_register_drm:
|
|
phytium_pci_vram_fini(pdev, priv);
|
|
failed_pci_vram_init:
|
|
phytium_pci_private_fini(pdev, priv);
|
|
failed_pci_private_init:
|
|
if (pdev->msi_enabled)
|
|
pci_disable_msi(pdev);
|
|
pci_disable_device(pdev);
|
|
failed_enable_device:
|
|
pci_set_drvdata(pdev, NULL);
|
|
drm_dev_put(dev);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void phytium_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct drm_device *dev = pci_get_drvdata(pdev);
|
|
struct phytium_display_private *priv = dev->dev_private;
|
|
|
|
phytium_dp_hpd_irq_setup(dev, false);
|
|
cancel_work_sync(&priv->hotplug_work);
|
|
drm_dev_unregister(dev);
|
|
phytium_pci_vram_fini(pdev, priv);
|
|
phytium_pci_private_fini(pdev, priv);
|
|
if (pdev->msi_enabled)
|
|
pci_disable_msi(pdev);
|
|
pci_disable_device(pdev);
|
|
pci_set_drvdata(pdev, NULL);
|
|
drm_dev_put(dev);
|
|
}
|
|
|
|
static void phytium_pci_shutdown(struct pci_dev *pdev)
|
|
{
|
|
struct drm_device *dev = pci_get_drvdata(pdev);
|
|
struct phytium_display_private *priv = dev->dev_private;
|
|
|
|
priv->display_shutdown(dev);
|
|
}
|
|
|
|
static int phytium_pci_pm_suspend(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct drm_device *drm_dev = pci_get_drvdata(pdev);
|
|
struct phytium_display_private *priv = drm_dev->dev_private;
|
|
int ret = 0;
|
|
|
|
if (IS_PX210(priv))
|
|
phytium_pci_dma_init(priv);
|
|
|
|
ret = priv->display_pm_suspend(drm_dev);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
pci_save_state(pdev);
|
|
pci_disable_device(pdev);
|
|
pci_set_power_state(pdev, PCI_D3hot);
|
|
udelay(200);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int phytium_pci_pm_resume(struct device *dev)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct drm_device *drm_dev = pci_get_drvdata(pdev);
|
|
struct phytium_display_private *priv = drm_dev->dev_private;
|
|
int ret = 0;
|
|
|
|
pci_set_power_state(pdev, PCI_D0);
|
|
pci_restore_state(pdev);
|
|
ret = pci_enable_device(pdev);
|
|
if (ret)
|
|
return ret;
|
|
pci_set_master(pdev);
|
|
|
|
ret = priv->display_pm_resume(drm_dev);
|
|
if (IS_PX210(priv))
|
|
phytium_pci_dma_fini(priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct dev_pm_ops phytium_pci_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(phytium_pci_pm_suspend, phytium_pci_pm_resume)
|
|
};
|
|
|
|
static const struct phytium_device_info px210_info = {
|
|
.platform_mask = BIT(PHYTIUM_PLATFORM_PX210),
|
|
.total_pipes = 3,
|
|
.crtc_clock_max = PX210_DC_PIX_CLOCK_MAX,
|
|
.hdisplay_max = PX210_DC_HDISPLAY_MAX,
|
|
.vdisplay_max = PX210_DC_VDISPLAY_MAX,
|
|
.address_mask = PX210_DC_ADDRESS_MASK,
|
|
.backlight_max = PX210_DP_BACKLIGHT_MAX,
|
|
};
|
|
|
|
static const struct phytium_device_info pe220x_info = {
|
|
.platform_mask = BIT(PHYTIUM_PLATFORM_PE220X),
|
|
.total_pipes = 2,
|
|
.crtc_clock_max = PE220X_DC_PIX_CLOCK_MAX,
|
|
.hdisplay_max = PE220X_DC_HDISPLAY_MAX,
|
|
.vdisplay_max = PE220X_DC_VDISPLAY_MAX,
|
|
.address_mask = PE220X_DC_ADDRESS_MASK,
|
|
.backlight_max = PE220X_DP_BACKLIGHT_MAX,
|
|
};
|
|
|
|
static const struct pci_device_id phytium_display_pci_ids[] = {
|
|
{ PCI_VDEVICE(PHYTIUM, 0xdc22), (kernel_ulong_t)&px210_info },
|
|
{ PCI_VDEVICE(PHYTIUM, 0xdc3e), (kernel_ulong_t)&pe220x_info },
|
|
{ /* End: all zeroes */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, phytium_display_pci_ids);
|
|
|
|
struct pci_driver phytium_pci_driver = {
|
|
.name = "phytium_display_pci",
|
|
.id_table = phytium_display_pci_ids,
|
|
.probe = phytium_pci_probe,
|
|
.remove = phytium_pci_remove,
|
|
.shutdown = phytium_pci_shutdown,
|
|
.driver.pm = &phytium_pci_pm_ops,
|
|
};
|