mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
4.6 KiB
243 lines
4.6 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* AMD Passthru DMA device driver |
|
* -- Based on the CCP driver |
|
* |
|
* Copyright (C) 2016,2021 Advanced Micro Devices, Inc. |
|
* |
|
* Author: Sanjay R Mehta <[email protected]> |
|
* Author: Tom Lendacky <[email protected]> |
|
* Author: Gary R Hook <[email protected]> |
|
*/ |
|
|
|
#include <linux/device.h> |
|
#include <linux/dma-mapping.h> |
|
#include <linux/delay.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/kernel.h> |
|
#include <linux/kthread.h> |
|
#include <linux/module.h> |
|
#include <linux/pci_ids.h> |
|
#include <linux/pci.h> |
|
#include <linux/spinlock.h> |
|
|
|
#include "ptdma.h" |
|
|
|
struct pt_msix { |
|
int msix_count; |
|
struct msix_entry msix_entry; |
|
}; |
|
|
|
/* |
|
* pt_alloc_struct - allocate and initialize the pt_device struct |
|
* |
|
* @dev: device struct of the PTDMA |
|
*/ |
|
static struct pt_device *pt_alloc_struct(struct device *dev) |
|
{ |
|
struct pt_device *pt; |
|
|
|
pt = devm_kzalloc(dev, sizeof(*pt), GFP_KERNEL); |
|
|
|
if (!pt) |
|
return NULL; |
|
pt->dev = dev; |
|
|
|
INIT_LIST_HEAD(&pt->cmd); |
|
|
|
return pt; |
|
} |
|
|
|
static int pt_get_msix_irqs(struct pt_device *pt) |
|
{ |
|
struct pt_msix *pt_msix = pt->pt_msix; |
|
struct device *dev = pt->dev; |
|
struct pci_dev *pdev = to_pci_dev(dev); |
|
int ret; |
|
|
|
pt_msix->msix_entry.entry = 0; |
|
|
|
ret = pci_enable_msix_range(pdev, &pt_msix->msix_entry, 1, 1); |
|
if (ret < 0) |
|
return ret; |
|
|
|
pt_msix->msix_count = ret; |
|
|
|
pt->pt_irq = pt_msix->msix_entry.vector; |
|
|
|
return 0; |
|
} |
|
|
|
static int pt_get_msi_irq(struct pt_device *pt) |
|
{ |
|
struct device *dev = pt->dev; |
|
struct pci_dev *pdev = to_pci_dev(dev); |
|
int ret; |
|
|
|
ret = pci_enable_msi(pdev); |
|
if (ret) |
|
return ret; |
|
|
|
pt->pt_irq = pdev->irq; |
|
|
|
return 0; |
|
} |
|
|
|
static int pt_get_irqs(struct pt_device *pt) |
|
{ |
|
struct device *dev = pt->dev; |
|
int ret; |
|
|
|
ret = pt_get_msix_irqs(pt); |
|
if (!ret) |
|
return 0; |
|
|
|
/* Couldn't get MSI-X vectors, try MSI */ |
|
dev_err(dev, "could not enable MSI-X (%d), trying MSI\n", ret); |
|
ret = pt_get_msi_irq(pt); |
|
if (!ret) |
|
return 0; |
|
|
|
/* Couldn't get MSI interrupt */ |
|
dev_err(dev, "could not enable MSI (%d)\n", ret); |
|
|
|
return ret; |
|
} |
|
|
|
static void pt_free_irqs(struct pt_device *pt) |
|
{ |
|
struct pt_msix *pt_msix = pt->pt_msix; |
|
struct device *dev = pt->dev; |
|
struct pci_dev *pdev = to_pci_dev(dev); |
|
|
|
if (pt_msix->msix_count) |
|
pci_disable_msix(pdev); |
|
else if (pt->pt_irq) |
|
pci_disable_msi(pdev); |
|
|
|
pt->pt_irq = 0; |
|
} |
|
|
|
static int pt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
|
{ |
|
struct pt_device *pt; |
|
struct pt_msix *pt_msix; |
|
struct device *dev = &pdev->dev; |
|
void __iomem * const *iomap_table; |
|
int bar_mask; |
|
int ret = -ENOMEM; |
|
|
|
pt = pt_alloc_struct(dev); |
|
if (!pt) |
|
goto e_err; |
|
|
|
pt_msix = devm_kzalloc(dev, sizeof(*pt_msix), GFP_KERNEL); |
|
if (!pt_msix) |
|
goto e_err; |
|
|
|
pt->pt_msix = pt_msix; |
|
pt->dev_vdata = (struct pt_dev_vdata *)id->driver_data; |
|
if (!pt->dev_vdata) { |
|
ret = -ENODEV; |
|
dev_err(dev, "missing driver data\n"); |
|
goto e_err; |
|
} |
|
|
|
ret = pcim_enable_device(pdev); |
|
if (ret) { |
|
dev_err(dev, "pcim_enable_device failed (%d)\n", ret); |
|
goto e_err; |
|
} |
|
|
|
bar_mask = pci_select_bars(pdev, IORESOURCE_MEM); |
|
ret = pcim_iomap_regions(pdev, bar_mask, "ptdma"); |
|
if (ret) { |
|
dev_err(dev, "pcim_iomap_regions failed (%d)\n", ret); |
|
goto e_err; |
|
} |
|
|
|
iomap_table = pcim_iomap_table(pdev); |
|
if (!iomap_table) { |
|
dev_err(dev, "pcim_iomap_table failed\n"); |
|
ret = -ENOMEM; |
|
goto e_err; |
|
} |
|
|
|
pt->io_regs = iomap_table[pt->dev_vdata->bar]; |
|
if (!pt->io_regs) { |
|
dev_err(dev, "ioremap failed\n"); |
|
ret = -ENOMEM; |
|
goto e_err; |
|
} |
|
|
|
ret = pt_get_irqs(pt); |
|
if (ret) |
|
goto e_err; |
|
|
|
pci_set_master(pdev); |
|
|
|
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)); |
|
if (ret) { |
|
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); |
|
if (ret) { |
|
dev_err(dev, "dma_set_mask_and_coherent failed (%d)\n", |
|
ret); |
|
goto e_err; |
|
} |
|
} |
|
|
|
dev_set_drvdata(dev, pt); |
|
|
|
if (pt->dev_vdata) |
|
ret = pt_core_init(pt); |
|
|
|
if (ret) |
|
goto e_err; |
|
|
|
return 0; |
|
|
|
e_err: |
|
dev_err(dev, "initialization failed ret = %d\n", ret); |
|
|
|
return ret; |
|
} |
|
|
|
static void pt_pci_remove(struct pci_dev *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct pt_device *pt = dev_get_drvdata(dev); |
|
|
|
if (!pt) |
|
return; |
|
|
|
if (pt->dev_vdata) |
|
pt_core_destroy(pt); |
|
|
|
pt_free_irqs(pt); |
|
} |
|
|
|
static const struct pt_dev_vdata dev_vdata[] = { |
|
{ |
|
.bar = 2, |
|
}, |
|
}; |
|
|
|
static const struct pci_device_id pt_pci_table[] = { |
|
{ PCI_VDEVICE(AMD, 0x1498), (kernel_ulong_t)&dev_vdata[0] }, |
|
/* Last entry must be zero */ |
|
{ 0, } |
|
}; |
|
MODULE_DEVICE_TABLE(pci, pt_pci_table); |
|
|
|
static struct pci_driver pt_pci_driver = { |
|
.name = "ptdma", |
|
.id_table = pt_pci_table, |
|
.probe = pt_pci_probe, |
|
.remove = pt_pci_remove, |
|
}; |
|
|
|
module_pci_driver(pt_pci_driver); |
|
|
|
MODULE_AUTHOR("Sanjay R Mehta <[email protected]>"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_DESCRIPTION("AMD PassThru DMA driver");
|
|
|