/*
* Support PCI/PCIe on PowerNV platforms
*
* Copyright 2011 Benjamin Herrenschmidt, IBM Corp.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/msi.h>
#include <linux/iommu.h>
#include <asm/sections.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/msi_bitmap.h>
#include <asm/ppc-pci.h>
#include <asm/pnv-pci.h>
#include <asm/opal.h>
#include <asm/iommu.h>
#include <asm/tce.h>
#include <asm/firmware.h>
#include <asm/eeh_event.h>
#include <asm/eeh.h>
#include "powernv.h"
#include "pci.h"
int pnv_pci_get_slot_id(struct device_node *np, uint64_t *id)
{
struct device_node *parent = np;
u32 bdfn;
u64 phbid;
int ret;
ret = of_property_read_u32(np, "reg", &bdfn);
if (ret)
return -ENXIO;
bdfn = ((bdfn & 0x00ffff00) >> 8);
while ((parent = of_get_parent(parent))) {
if (!PCI_DN(parent)) {
of_node_put(parent);
break;
}
if (!of_device_is_compatible(parent, "ibm,ioda2-phb")) {
of_node_put(parent);
continue;
}
ret = of_property_read_u64(parent, "ibm,opal-phbid", &phbid);
if (ret) {
of_node_put(parent);
return -ENXIO;
}
*id = PCI_SLOT_ID(phbid, bdfn);
return 0;
}
return -ENODEV;
}
EXPORT_SYMBOL_GPL(pnv_pci_get_slot_id);
int pnv_pci_get_device_tree(uint32_t phandle, void *buf, uint64_t len)
{
int64_t rc;
if (!opal_check_token(OPAL_GET_DEVICE_TREE))
return -ENXIO;
rc = opal_get_device_tree(phandle, (uint64_t)buf, len);
if (rc < OPAL_SUCCESS)
return -EIO;
return rc;
}
EXPORT_SYMBOL_GPL(pnv_pci_get_device_tree);
int pnv_pci_get_presence_state(uint64_t id, uint8_t *state)
{
int64_t rc;
if (!opal_check_token(OPAL_PCI_GET_PRESENCE_STATE))
return -ENXIO;
rc = opal_pci_get_presence_state(id, (uint64_t)state);
if (rc != OPAL_SUCCESS)
return -EIO;
return 0;
}
EXPORT_SYMBOL_GPL(pnv_pci_get_presence_state);
int pnv_pci_get_power_state(uint64_t id, uint8_t *state)
{
int64_t rc;
if (!opal_check_token(OPAL_PCI_GET_POWER_STATE))
return -ENXIO;
rc = opal_pci_get_power_state(id, (uint64_t)state);
if (rc != OPAL_SUCCESS)
return -EIO;
return 0;
}
EXPORT_SYMBOL_GPL(pnv_pci_get_power_state);
int pnv_pci_set_power_state(uint64_t id, uint8_t state, struct opal_msg *msg)
{
struct opal_msg m;
int token, ret;
int64_t rc;
if (!opal_check_token(OPAL_PCI_SET_POWER_STATE))
return -ENXIO;
token = opal_async_get_token_interruptible();
if (unlikely(token < 0))
return token;
rc = opal_pci_set_power_state(token, id, (uint64_t)&state);
if (rc == OPAL_SUCCESS) {
ret = 0;
goto exit;
} else if (rc != OPAL_ASYNC_COMPLETION) {
ret = -EIO;
goto exit;
}
ret = opal_async_wait_response(token, &m);
if (ret < 0)
goto exit;
if (msg) {
ret = 1;
memcpy(msg, &m, sizeof(m));
}
exit:
opal_async_release_token(token);
return ret;
}
EXPORT_SYMBOL_GPL(pnv_pci_set_power_state);
#ifdef CONFIG_PCI_MSI
int pnv_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
{
struct pci_controller *hose = pci_bus_to_host(pdev->bus);
struct pnv_phb *phb = hose->private_data;
struct msi_desc *entry;
struct msi_msg msg;
int hwirq;
unsigned int virq;
int rc;
if (WARN_ON(!phb) || !phb->msi_bmp.bitmap)
return -ENODEV;
if