mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2025-12-25 09:11:49 +00:00
Retrieve from the firmware the DMA mask value we need to set according to the device's PCI controller configuration. This is needed when working on POWER9 machines, as the device's PCI controller is configured in a different way in those machines. Reviewed-by: Tomer Tayar <ttayar@habana.ai> Signed-off-by: Oded Gabbay <oded.gabbay@gmail.com>
410 lines
10 KiB
C
410 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
* Copyright 2016-2019 HabanaLabs, Ltd.
|
|
* All Rights Reserved.
|
|
*/
|
|
|
|
#include "habanalabs.h"
|
|
#include "include/hw_ip/pci/pci_general.h"
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#define HL_PLDM_PCI_ELBI_TIMEOUT_MSEC (HL_PCI_ELBI_TIMEOUT_MSEC * 10)
|
|
|
|
/**
|
|
* hl_pci_bars_map() - Map PCI BARs.
|
|
* @hdev: Pointer to hl_device structure.
|
|
* @bar_name: Array of BAR names.
|
|
* @is_wc: Array with flag per BAR whether a write-combined mapping is needed.
|
|
*
|
|
* Request PCI regions and map them to kernel virtual addresses.
|
|
*
|
|
* Return: 0 on success, non-zero for failure.
|
|
*/
|
|
int hl_pci_bars_map(struct hl_device *hdev, const char * const name[3],
|
|
bool is_wc[3])
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
int rc, i, bar;
|
|
|
|
rc = pci_request_regions(pdev, HL_NAME);
|
|
if (rc) {
|
|
dev_err(hdev->dev, "Cannot obtain PCI resources\n");
|
|
return rc;
|
|
}
|
|
|
|
for (i = 0 ; i < 3 ; i++) {
|
|
bar = i * 2; /* 64-bit BARs */
|
|
hdev->pcie_bar[bar] = is_wc[i] ?
|
|
pci_ioremap_wc_bar(pdev, bar) :
|
|
pci_ioremap_bar(pdev, bar);
|
|
if (!hdev->pcie_bar[bar]) {
|
|
dev_err(hdev->dev, "pci_ioremap%s_bar failed for %s\n",
|
|
is_wc[i] ? "_wc" : "", name[i]);
|
|
rc = -ENODEV;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
for (i = 2 ; i >= 0 ; i--) {
|
|
bar = i * 2; /* 64-bit BARs */
|
|
if (hdev->pcie_bar[bar])
|
|
iounmap(hdev->pcie_bar[bar]);
|
|
}
|
|
|
|
pci_release_regions(pdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* hl_pci_bars_unmap() - Unmap PCI BARS.
|
|
* @hdev: Pointer to hl_device structure.
|
|
*
|
|
* Release all PCI BARs and unmap their virtual addresses.
|
|
*/
|
|
static void hl_pci_bars_unmap(struct hl_device *hdev)
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
int i, bar;
|
|
|
|
for (i = 2 ; i >= 0 ; i--) {
|
|
bar = i * 2; /* 64-bit BARs */
|
|
iounmap(hdev->pcie_bar[bar]);
|
|
}
|
|
|
|
pci_release_regions(pdev);
|
|
}
|
|
|
|
/*
|
|
* hl_pci_elbi_write() - Write through the ELBI interface.
|
|
* @hdev: Pointer to hl_device structure.
|
|
*
|
|
* Return: 0 on success, negative value for failure.
|
|
*/
|
|
static int hl_pci_elbi_write(struct hl_device *hdev, u64 addr, u32 data)
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
ktime_t timeout;
|
|
u64 msec;
|
|
u32 val;
|
|
|
|
if (hdev->pldm)
|
|
msec = HL_PLDM_PCI_ELBI_TIMEOUT_MSEC;
|
|
else
|
|
msec = HL_PCI_ELBI_TIMEOUT_MSEC;
|
|
|
|
/* Clear previous status */
|
|
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, 0);
|
|
|
|
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_ADDR, (u32) addr);
|
|
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_DATA, data);
|
|
pci_write_config_dword(pdev, mmPCI_CONFIG_ELBI_CTRL,
|
|
PCI_CONFIG_ELBI_CTRL_WRITE);
|
|
|
|
timeout = ktime_add_ms(ktime_get(), msec);
|
|
for (;;) {
|
|
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS, &val);
|
|
if (val & PCI_CONFIG_ELBI_STS_MASK)
|
|
break;
|
|
if (ktime_compare(ktime_get(), timeout) > 0) {
|
|
pci_read_config_dword(pdev, mmPCI_CONFIG_ELBI_STS,
|
|
&val);
|
|
break;
|
|
}
|
|
|
|
usleep_range(300, 500);
|
|
}
|
|
|
|
if ((val & PCI_CONFIG_ELBI_STS_MASK) == PCI_CONFIG_ELBI_STS_DONE)
|
|
return 0;
|
|
|
|
if (val & PCI_CONFIG_ELBI_STS_ERR) {
|
|
dev_err(hdev->dev, "Error writing to ELBI\n");
|
|
return -EIO;
|
|
}
|
|
|
|
if (!(val & PCI_CONFIG_ELBI_STS_MASK)) {
|
|
dev_err(hdev->dev, "ELBI write didn't finish in time\n");
|
|
return -EIO;
|
|
}
|
|
|
|
dev_err(hdev->dev, "ELBI write has undefined bits in status\n");
|
|
return -EIO;
|
|
}
|
|
|
|
/**
|
|
* hl_pci_iatu_write() - iatu write routine.
|
|
* @hdev: Pointer to hl_device structure.
|
|
*
|
|
* Return: 0 on success, negative value for failure.
|
|
*/
|
|
int hl_pci_iatu_write(struct hl_device *hdev, u32 addr, u32 data)
|
|
{
|
|
struct asic_fixed_properties *prop = &hdev->asic_prop;
|
|
u32 dbi_offset;
|
|
int rc;
|
|
|
|
dbi_offset = addr & 0xFFF;
|
|
|
|
rc = hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0x00300000);
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_dbi_base_address + dbi_offset,
|
|
data);
|
|
|
|
if (rc)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* hl_pci_reset_link_through_bridge() - Reset PCI link.
|
|
* @hdev: Pointer to hl_device structure.
|
|
*/
|
|
static void hl_pci_reset_link_through_bridge(struct hl_device *hdev)
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
struct pci_dev *parent_port;
|
|
u16 val;
|
|
|
|
parent_port = pdev->bus->self;
|
|
pci_read_config_word(parent_port, PCI_BRIDGE_CONTROL, &val);
|
|
val |= PCI_BRIDGE_CTL_BUS_RESET;
|
|
pci_write_config_word(parent_port, PCI_BRIDGE_CONTROL, val);
|
|
ssleep(1);
|
|
|
|
val &= ~(PCI_BRIDGE_CTL_BUS_RESET);
|
|
pci_write_config_word(parent_port, PCI_BRIDGE_CONTROL, val);
|
|
ssleep(3);
|
|
}
|
|
|
|
/**
|
|
* hl_pci_set_dram_bar_base() - Set DDR BAR to map specific device address.
|
|
* @hdev: Pointer to hl_device structure.
|
|
* @inbound_region: Inbound region number.
|
|
* @bar: PCI BAR number.
|
|
* @addr: Address in DRAM. Must be aligned to DRAM bar size.
|
|
*
|
|
* Configure the iATU so that the DRAM bar will start at the specified address.
|
|
*
|
|
* Return: 0 on success, negative value for failure.
|
|
*/
|
|
int hl_pci_set_dram_bar_base(struct hl_device *hdev, u8 inbound_region, u8 bar,
|
|
u64 addr)
|
|
{
|
|
struct asic_fixed_properties *prop = &hdev->asic_prop;
|
|
u32 offset;
|
|
int rc;
|
|
|
|
switch (inbound_region) {
|
|
case 0:
|
|
offset = 0x100;
|
|
break;
|
|
case 1:
|
|
offset = 0x300;
|
|
break;
|
|
case 2:
|
|
offset = 0x500;
|
|
break;
|
|
default:
|
|
dev_err(hdev->dev, "Invalid inbound region %d\n",
|
|
inbound_region);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bar != 0 && bar != 2 && bar != 4) {
|
|
dev_err(hdev->dev, "Invalid PCI BAR %d\n", bar);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Point to the specified address */
|
|
rc = hl_pci_iatu_write(hdev, offset + 0x14, lower_32_bits(addr));
|
|
rc |= hl_pci_iatu_write(hdev, offset + 0x18, upper_32_bits(addr));
|
|
rc |= hl_pci_iatu_write(hdev, offset + 0x0, 0);
|
|
/* Enable + BAR match + match enable + BAR number */
|
|
rc |= hl_pci_iatu_write(hdev, offset + 0x4, 0xC0080000 | (bar << 8));
|
|
|
|
/* Return the DBI window to the default location */
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0);
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr + 4, 0);
|
|
|
|
if (rc)
|
|
dev_err(hdev->dev, "failed to map DRAM bar to 0x%08llx\n",
|
|
addr);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* hl_pci_init_iatu() - Initialize the iATU unit inside the PCI controller.
|
|
* @hdev: Pointer to hl_device structure.
|
|
* @sram_base_address: SRAM base address.
|
|
* @dram_base_address: DRAM base address.
|
|
* @host_phys_base_address: Base physical address of host memory for device
|
|
* transactions.
|
|
* @host_phys_size: Size of host memory for device transactions.
|
|
*
|
|
* This is needed in case the firmware doesn't initialize the iATU.
|
|
*
|
|
* Return: 0 on success, negative value for failure.
|
|
*/
|
|
int hl_pci_init_iatu(struct hl_device *hdev, u64 sram_base_address,
|
|
u64 dram_base_address, u64 host_phys_base_address,
|
|
u64 host_phys_size)
|
|
{
|
|
struct asic_fixed_properties *prop = &hdev->asic_prop;
|
|
u64 host_phys_end_addr;
|
|
int rc = 0;
|
|
|
|
/* Inbound Region 0 - Bar 0 - Point to SRAM base address */
|
|
rc = hl_pci_iatu_write(hdev, 0x114, lower_32_bits(sram_base_address));
|
|
rc |= hl_pci_iatu_write(hdev, 0x118, upper_32_bits(sram_base_address));
|
|
rc |= hl_pci_iatu_write(hdev, 0x100, 0);
|
|
/* Enable + Bar match + match enable */
|
|
rc |= hl_pci_iatu_write(hdev, 0x104, 0xC0080000);
|
|
|
|
/* Return the DBI window to the default location */
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0);
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr + 4, 0);
|
|
|
|
hdev->asic_funcs->set_dma_mask_from_fw(hdev);
|
|
|
|
/* Point to DRAM */
|
|
if (!hdev->asic_funcs->set_dram_bar_base)
|
|
return -EINVAL;
|
|
if (hdev->asic_funcs->set_dram_bar_base(hdev, dram_base_address) ==
|
|
U64_MAX)
|
|
return -EIO;
|
|
|
|
/* Outbound Region 0 - Point to Host */
|
|
host_phys_end_addr = host_phys_base_address + host_phys_size - 1;
|
|
rc |= hl_pci_iatu_write(hdev, 0x008,
|
|
lower_32_bits(host_phys_base_address));
|
|
rc |= hl_pci_iatu_write(hdev, 0x00C,
|
|
upper_32_bits(host_phys_base_address));
|
|
rc |= hl_pci_iatu_write(hdev, 0x010, lower_32_bits(host_phys_end_addr));
|
|
rc |= hl_pci_iatu_write(hdev, 0x014, 0);
|
|
|
|
if ((hdev->power9_64bit_dma_enable) && (hdev->dma_mask == 64))
|
|
rc |= hl_pci_iatu_write(hdev, 0x018, 0x08000000);
|
|
else
|
|
rc |= hl_pci_iatu_write(hdev, 0x018, 0);
|
|
|
|
rc |= hl_pci_iatu_write(hdev, 0x020, upper_32_bits(host_phys_end_addr));
|
|
/* Increase region size */
|
|
rc |= hl_pci_iatu_write(hdev, 0x000, 0x00002000);
|
|
/* Enable */
|
|
rc |= hl_pci_iatu_write(hdev, 0x004, 0x80000000);
|
|
|
|
/* Return the DBI window to the default location */
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr, 0);
|
|
rc |= hl_pci_elbi_write(hdev, prop->pcie_aux_dbi_reg_addr + 4, 0);
|
|
|
|
if (rc)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hl_pci_set_dma_mask() - Set DMA masks for the device.
|
|
* @hdev: Pointer to hl_device structure.
|
|
* @dma_mask: number of bits for the requested dma mask.
|
|
*
|
|
* This function sets the DMA masks (regular and consistent) for a specified
|
|
* value. If it doesn't succeed, it tries to set it to a fall-back value
|
|
*
|
|
* Return: 0 on success, non-zero for failure.
|
|
*/
|
|
int hl_pci_set_dma_mask(struct hl_device *hdev)
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
int rc;
|
|
|
|
/* set DMA mask */
|
|
rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(hdev->dma_mask));
|
|
if (rc) {
|
|
dev_err(hdev->dev,
|
|
"Failed to set pci dma mask to %d bits, error %d\n",
|
|
hdev->dma_mask, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(hdev->dma_mask));
|
|
if (rc) {
|
|
dev_err(hdev->dev,
|
|
"Failed to set pci consistent dma mask to %d bits, error %d\n",
|
|
hdev->dma_mask, rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* hl_pci_init() - PCI initialization code.
|
|
* @hdev: Pointer to hl_device structure.
|
|
*
|
|
* Set DMA masks, initialize the PCI controller and map the PCI BARs.
|
|
*
|
|
* Return: 0 on success, non-zero for failure.
|
|
*/
|
|
int hl_pci_init(struct hl_device *hdev)
|
|
{
|
|
struct pci_dev *pdev = hdev->pdev;
|
|
int rc;
|
|
|
|
if (hdev->reset_pcilink)
|
|
hl_pci_reset_link_through_bridge(hdev);
|
|
|
|
rc = pci_enable_device_mem(pdev);
|
|
if (rc) {
|
|
dev_err(hdev->dev, "can't enable PCI device\n");
|
|
return rc;
|
|
}
|
|
|
|
pci_set_master(pdev);
|
|
|
|
rc = hdev->asic_funcs->pci_bars_map(hdev);
|
|
if (rc) {
|
|
dev_err(hdev->dev, "Failed to initialize PCI BARs\n");
|
|
goto disable_device;
|
|
}
|
|
|
|
rc = hdev->asic_funcs->init_iatu(hdev);
|
|
if (rc) {
|
|
dev_err(hdev->dev, "Failed to initialize iATU\n");
|
|
goto disable_device;
|
|
}
|
|
|
|
rc = hl_pci_set_dma_mask(hdev);
|
|
if (rc)
|
|
goto disable_device;
|
|
|
|
return 0;
|
|
|
|
disable_device:
|
|
pci_clear_master(pdev);
|
|
pci_disable_device(pdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* hl_fw_fini() - PCI finalization code.
|
|
* @hdev: Pointer to hl_device structure
|
|
*
|
|
* Unmap PCI bars and disable PCI device.
|
|
*/
|
|
void hl_pci_fini(struct hl_device *hdev)
|
|
{
|
|
hl_pci_bars_unmap(hdev);
|
|
|
|
pci_clear_master(hdev->pdev);
|
|
pci_disable_device(hdev->pdev);
|
|
}
|