// SPDX-License-Identifier: GPL-2.0+
// Copyright 2017 IBM Corp.
#include <linux/pci.h>
#include <asm/pnv-ocxl.h>
#include <misc/ocxl-config.h>
#include "ocxl_internal.h"
#define EXTRACT_BIT(val, bit) (!!(val & BIT(bit)))
#define EXTRACT_BITS(val, s, e) ((val & GENMASK(e, s)) >> s)
#define OCXL_DVSEC_AFU_IDX_MASK GENMASK(5, 0)
#define OCXL_DVSEC_ACTAG_MASK GENMASK(11, 0)
#define OCXL_DVSEC_PASID_MASK GENMASK(19, 0)
#define OCXL_DVSEC_PASID_LOG_MASK GENMASK(4, 0)
#define OCXL_DVSEC_TEMPL_VERSION 0x0
#define OCXL_DVSEC_TEMPL_NAME 0x4
#define OCXL_DVSEC_TEMPL_AFU_VERSION 0x1C
#define OCXL_DVSEC_TEMPL_MMIO_GLOBAL 0x20
#define OCXL_DVSEC_TEMPL_MMIO_GLOBAL_SZ 0x28
#define OCXL_DVSEC_TEMPL_MMIO_PP 0x30
#define OCXL_DVSEC_TEMPL_MMIO_PP_SZ 0x38
#define OCXL_DVSEC_TEMPL_ALL_MEM_SZ 0x3C
#define OCXL_DVSEC_TEMPL_LPC_MEM_START 0x40
#define OCXL_DVSEC_TEMPL_WWID 0x48
#define OCXL_DVSEC_TEMPL_LPC_MEM_SZ 0x58
#define OCXL_MAX_AFU_PER_FUNCTION 64
#define OCXL_TEMPL_LEN_1_0 0x58
#define OCXL_TEMPL_LEN_1_1 0x60
#define OCXL_TEMPL_NAME_LEN 24
#define OCXL_CFG_TIMEOUT 3
static int find_dvsec(struct pci_dev *dev, int dvsec_id)
{
int vsec = 0;
u16 vendor, id;
while ((vsec = pci_find_next_ext_capability(dev, vsec,
OCXL_EXT_CAP_ID_DVSEC))) {
pci_read_config_word(dev, vsec + OCXL_DVSEC_VENDOR_OFFSET,
&vendor);
pci_read_config_word(dev, vsec + OCXL_DVSEC_ID_OFFSET, &id);
if (vendor == PCI_VENDOR_ID_IBM && id == dvsec_id)
return vsec;
}
return 0;
}
static int find_dvsec_afu_ctrl(struct pci_dev *dev, u8 afu_idx)
{
int vsec = 0;
u16 vendor, id;
u8 idx;
while ((vsec = pci_find_next_ext_capability(dev, vsec,
OCXL_EXT_CAP_ID_DVSEC))) {
pci_read_config_word(dev, vsec + OCXL_DVSEC_VENDOR_OFFSET,
&vendor);
pci_read_config_word(dev, vsec + OCXL_DVSEC_ID_OFFSET, &id);
if (vendor == PCI_VENDOR_ID_IBM &&
id == OCXL_DVSEC_AFU_CTRL_ID) {
pci_read_config_byte(dev,
vsec + OCXL_DVSEC_AFU_CTRL_AFU_IDX,
&idx);
if (idx == afu_idx)
return vsec;
}
}
return 0;
}
/**
* get_function_0() - Find a related PCI device (function 0)
* @dev: PCI device to match
*
* Returns a pointer to the related device, or null if not found
*/
static struct pci_dev *get_function_0(struct pci_dev *dev)
{
unsigned int devfn = PCI_DEVFN(PCI_SLOT(dev->devfn), 0);
return pci_get_domain_bus_and_slot(pci_domain_nr(dev->bus),
dev->bus->number, devfn);
}
static void read_pasid(struct pci_dev *dev, struct ocxl_fn_config *fn)
{
u16 val;
int pos;
pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_PASID);
if (!pos) {
/*
* PASID capability is not mandatory, but there
* shouldn't be any AFU
*/
dev_dbg(&dev->dev, "Function doesn't require any PASID\n");
fn->max_pasid_log = -1;
goto out;
}
pci_read_config_word(dev, pos + PCI_PASID_CAP, &val);
fn->max_pasid_log = EXTRACT_BITS(val, 8, 12);
out:
dev_dbg(&dev->dev, "PASID capability:\n");
dev_dbg(&dev->dev, " Max PASID log = %d\n", fn->max_pasid_log);
}
static int read_dvsec_tl(struct pci_dev *dev, struct ocxl_fn_config *fn)
{
int pos;
pos = find_dvsec(dev, OCXL_DVSEC_TL_ID);
if (!pos && PCI_FUNC(dev->devfn) == 0) {
dev_err(&dev->dev, "Can't find TL DVSEC\n");
return -ENODEV;
}
if (pos && PCI_FUNC(dev->devfn) != 0) {
dev_err(&dev->dev, "TL DVSEC is only allowed on function 0\n");
return -ENODEV;
}
fn->dvsec_tl_pos = pos;
return 0;
}
static int read_dvsec_function(struct pci_dev *dev, struct ocxl_fn_config *fn)
{
int pos, afu_present;
u32 val;
pos = find_dvsec(dev, OCXL_DVSEC_FUNC_ID);
if (!pos) {
dev_err(&dev->dev, "Can't find function DVSEC\n");
return -ENODEV;
}
fn->dvsec_function_pos = pos;
pci_read_config_dword(dev, pos + OCXL_DVSEC_FUNC_OFF_INDEX, &val);
afu_present = EXTRACT_BIT(val, 31);
if (!afu_present) {
fn->max_afu_index = -1;
dev_dbg(&dev->dev, "Function doesn't define any AFU\n");
goto out;
}
fn->max_afu_index = EXTRACT_BITS(val, 24, 29);
out:
dev_dbg(&dev->dev, "Function DVSEC:\n");
dev_dbg(&dev->dev, " Max AFU index = %d\n", fn->max_afu_index);
return 0;
}
static int read_dvsec_afu_info(struct pci_dev *dev, struct ocxl_fn_config *fn)
{
int pos;
if (fn->max_afu_index < 0) {
fn->dvsec_afu_info_pos