162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PCI Hotplug Driver for PowerPC PowerNV platform. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright Gavin Shan, IBM Corporation 2016. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/libfdt.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/pci.h> 1162306a36Sopenharmony_ci#include <linux/pci_hotplug.h> 1262306a36Sopenharmony_ci#include <linux/of_fdt.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <asm/opal.h> 1562306a36Sopenharmony_ci#include <asm/pnv-pci.h> 1662306a36Sopenharmony_ci#include <asm/ppc-pci.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define DRIVER_VERSION "0.1" 1962306a36Sopenharmony_ci#define DRIVER_AUTHOR "Gavin Shan, IBM Corporation" 2062306a36Sopenharmony_ci#define DRIVER_DESC "PowerPC PowerNV PCI Hotplug Driver" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define SLOT_WARN(sl, x...) \ 2362306a36Sopenharmony_ci ((sl)->pdev ? pci_warn((sl)->pdev, x) : dev_warn(&(sl)->bus->dev, x)) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistruct pnv_php_event { 2662306a36Sopenharmony_ci bool added; 2762306a36Sopenharmony_ci struct pnv_php_slot *php_slot; 2862306a36Sopenharmony_ci struct work_struct work; 2962306a36Sopenharmony_ci}; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic LIST_HEAD(pnv_php_slot_list); 3262306a36Sopenharmony_cistatic DEFINE_SPINLOCK(pnv_php_lock); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic void pnv_php_register(struct device_node *dn); 3562306a36Sopenharmony_cistatic void pnv_php_unregister_one(struct device_node *dn); 3662306a36Sopenharmony_cistatic void pnv_php_unregister(struct device_node *dn); 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic void pnv_php_disable_irq(struct pnv_php_slot *php_slot, 3962306a36Sopenharmony_ci bool disable_device) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct pci_dev *pdev = php_slot->pdev; 4262306a36Sopenharmony_ci int irq = php_slot->irq; 4362306a36Sopenharmony_ci u16 ctrl; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci if (php_slot->irq > 0) { 4662306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl); 4762306a36Sopenharmony_ci ctrl &= ~(PCI_EXP_SLTCTL_HPIE | 4862306a36Sopenharmony_ci PCI_EXP_SLTCTL_PDCE | 4962306a36Sopenharmony_ci PCI_EXP_SLTCTL_DLLSCE); 5062306a36Sopenharmony_ci pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl); 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci free_irq(php_slot->irq, php_slot); 5362306a36Sopenharmony_ci php_slot->irq = 0; 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci if (php_slot->wq) { 5762306a36Sopenharmony_ci destroy_workqueue(php_slot->wq); 5862306a36Sopenharmony_ci php_slot->wq = NULL; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (disable_device || irq > 0) { 6262306a36Sopenharmony_ci if (pdev->msix_enabled) 6362306a36Sopenharmony_ci pci_disable_msix(pdev); 6462306a36Sopenharmony_ci else if (pdev->msi_enabled) 6562306a36Sopenharmony_ci pci_disable_msi(pdev); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci pci_disable_device(pdev); 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void pnv_php_free_slot(struct kref *kref) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct pnv_php_slot *php_slot = container_of(kref, 7462306a36Sopenharmony_ci struct pnv_php_slot, kref); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci WARN_ON(!list_empty(&php_slot->children)); 7762306a36Sopenharmony_ci pnv_php_disable_irq(php_slot, false); 7862306a36Sopenharmony_ci kfree(php_slot->name); 7962306a36Sopenharmony_ci kfree(php_slot); 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic inline void pnv_php_put_slot(struct pnv_php_slot *php_slot) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (!php_slot) 8662306a36Sopenharmony_ci return; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci kref_put(&php_slot->kref, pnv_php_free_slot); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic struct pnv_php_slot *pnv_php_match(struct device_node *dn, 9262306a36Sopenharmony_ci struct pnv_php_slot *php_slot) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct pnv_php_slot *target, *tmp; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci if (php_slot->dn == dn) { 9762306a36Sopenharmony_ci kref_get(&php_slot->kref); 9862306a36Sopenharmony_ci return php_slot; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci list_for_each_entry(tmp, &php_slot->children, link) { 10262306a36Sopenharmony_ci target = pnv_php_match(dn, tmp); 10362306a36Sopenharmony_ci if (target) 10462306a36Sopenharmony_ci return target; 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci return NULL; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistruct pnv_php_slot *pnv_php_find_slot(struct device_node *dn) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct pnv_php_slot *php_slot, *tmp; 11362306a36Sopenharmony_ci unsigned long flags; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci spin_lock_irqsave(&pnv_php_lock, flags); 11662306a36Sopenharmony_ci list_for_each_entry(tmp, &pnv_php_slot_list, link) { 11762306a36Sopenharmony_ci php_slot = pnv_php_match(dn, tmp); 11862306a36Sopenharmony_ci if (php_slot) { 11962306a36Sopenharmony_ci spin_unlock_irqrestore(&pnv_php_lock, flags); 12062306a36Sopenharmony_ci return php_slot; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci spin_unlock_irqrestore(&pnv_php_lock, flags); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return NULL; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pnv_php_find_slot); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci/* 13062306a36Sopenharmony_ci * Remove pdn for all children of the indicated device node. 13162306a36Sopenharmony_ci * The function should remove pdn in a depth-first manner. 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_cistatic void pnv_php_rmv_pdns(struct device_node *dn) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct device_node *child; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci for_each_child_of_node(dn, child) { 13862306a36Sopenharmony_ci pnv_php_rmv_pdns(child); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci pci_remove_device_node_info(child); 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/* 14562306a36Sopenharmony_ci * Detach all child nodes of the indicated device nodes. The 14662306a36Sopenharmony_ci * function should handle device nodes in depth-first manner. 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * We should not invoke of_node_release() as the memory for 14962306a36Sopenharmony_ci * individual device node is part of large memory block. The 15062306a36Sopenharmony_ci * large block is allocated from memblock (system bootup) or 15162306a36Sopenharmony_ci * kmalloc() when unflattening the device tree by OF changeset. 15262306a36Sopenharmony_ci * We can not free the large block allocated from memblock. For 15362306a36Sopenharmony_ci * later case, it should be released at once. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic void pnv_php_detach_device_nodes(struct device_node *parent) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci struct device_node *dn; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci for_each_child_of_node(parent, dn) { 16062306a36Sopenharmony_ci pnv_php_detach_device_nodes(dn); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci of_node_put(dn); 16362306a36Sopenharmony_ci of_detach_node(dn); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_cistatic void pnv_php_rmv_devtree(struct pnv_php_slot *php_slot) 16862306a36Sopenharmony_ci{ 16962306a36Sopenharmony_ci pnv_php_rmv_pdns(php_slot->dn); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* 17262306a36Sopenharmony_ci * Decrease the refcount if the device nodes were created 17362306a36Sopenharmony_ci * through OF changeset before detaching them. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci if (php_slot->fdt) 17662306a36Sopenharmony_ci of_changeset_destroy(&php_slot->ocs); 17762306a36Sopenharmony_ci pnv_php_detach_device_nodes(php_slot->dn); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (php_slot->fdt) { 18062306a36Sopenharmony_ci kfree(php_slot->dt); 18162306a36Sopenharmony_ci kfree(php_slot->fdt); 18262306a36Sopenharmony_ci php_slot->dt = NULL; 18362306a36Sopenharmony_ci php_slot->dn->child = NULL; 18462306a36Sopenharmony_ci php_slot->fdt = NULL; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* 18962306a36Sopenharmony_ci * As the nodes in OF changeset are applied in reverse order, we 19062306a36Sopenharmony_ci * need revert the nodes in advance so that we have correct node 19162306a36Sopenharmony_ci * order after the changeset is applied. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_cistatic void pnv_php_reverse_nodes(struct device_node *parent) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct device_node *child, *next; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci /* In-depth first */ 19862306a36Sopenharmony_ci for_each_child_of_node(parent, child) 19962306a36Sopenharmony_ci pnv_php_reverse_nodes(child); 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci /* Reverse the nodes in the child list */ 20262306a36Sopenharmony_ci child = parent->child; 20362306a36Sopenharmony_ci parent->child = NULL; 20462306a36Sopenharmony_ci while (child) { 20562306a36Sopenharmony_ci next = child->sibling; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci child->sibling = parent->child; 20862306a36Sopenharmony_ci parent->child = child; 20962306a36Sopenharmony_ci child = next; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic int pnv_php_populate_changeset(struct of_changeset *ocs, 21462306a36Sopenharmony_ci struct device_node *dn) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct device_node *child; 21762306a36Sopenharmony_ci int ret = 0; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci for_each_child_of_node(dn, child) { 22062306a36Sopenharmony_ci ret = of_changeset_attach_node(ocs, child); 22162306a36Sopenharmony_ci if (ret) { 22262306a36Sopenharmony_ci of_node_put(child); 22362306a36Sopenharmony_ci break; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci ret = pnv_php_populate_changeset(ocs, child); 22762306a36Sopenharmony_ci if (ret) { 22862306a36Sopenharmony_ci of_node_put(child); 22962306a36Sopenharmony_ci break; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return ret; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void *pnv_php_add_one_pdn(struct device_node *dn, void *data) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci struct pci_controller *hose = (struct pci_controller *)data; 23962306a36Sopenharmony_ci struct pci_dn *pdn; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci pdn = pci_add_device_node_info(hose, dn); 24262306a36Sopenharmony_ci if (!pdn) 24362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci return NULL; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cistatic void pnv_php_add_pdns(struct pnv_php_slot *slot) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci struct pci_controller *hose = pci_bus_to_host(slot->bus); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci pci_traverse_device_nodes(slot->dn, pnv_php_add_one_pdn, hose); 25362306a36Sopenharmony_ci} 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_cistatic int pnv_php_add_devtree(struct pnv_php_slot *php_slot) 25662306a36Sopenharmony_ci{ 25762306a36Sopenharmony_ci void *fdt, *fdt1, *dt; 25862306a36Sopenharmony_ci int ret; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* We don't know the FDT blob size. We try to get it through 26162306a36Sopenharmony_ci * maximal memory chunk and then copy it to another chunk that 26262306a36Sopenharmony_ci * fits the real size. 26362306a36Sopenharmony_ci */ 26462306a36Sopenharmony_ci fdt1 = kzalloc(0x10000, GFP_KERNEL); 26562306a36Sopenharmony_ci if (!fdt1) { 26662306a36Sopenharmony_ci ret = -ENOMEM; 26762306a36Sopenharmony_ci goto out; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci ret = pnv_pci_get_device_tree(php_slot->dn->phandle, fdt1, 0x10000); 27162306a36Sopenharmony_ci if (ret) { 27262306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d getting FDT blob\n", ret); 27362306a36Sopenharmony_ci goto free_fdt1; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci fdt = kmemdup(fdt1, fdt_totalsize(fdt1), GFP_KERNEL); 27762306a36Sopenharmony_ci if (!fdt) { 27862306a36Sopenharmony_ci ret = -ENOMEM; 27962306a36Sopenharmony_ci goto free_fdt1; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Unflatten device tree blob */ 28362306a36Sopenharmony_ci dt = of_fdt_unflatten_tree(fdt, php_slot->dn, NULL); 28462306a36Sopenharmony_ci if (!dt) { 28562306a36Sopenharmony_ci ret = -EINVAL; 28662306a36Sopenharmony_ci SLOT_WARN(php_slot, "Cannot unflatten FDT\n"); 28762306a36Sopenharmony_ci goto free_fdt; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci /* Initialize and apply the changeset */ 29162306a36Sopenharmony_ci of_changeset_init(&php_slot->ocs); 29262306a36Sopenharmony_ci pnv_php_reverse_nodes(php_slot->dn); 29362306a36Sopenharmony_ci ret = pnv_php_populate_changeset(&php_slot->ocs, php_slot->dn); 29462306a36Sopenharmony_ci if (ret) { 29562306a36Sopenharmony_ci pnv_php_reverse_nodes(php_slot->dn); 29662306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d populating changeset\n", 29762306a36Sopenharmony_ci ret); 29862306a36Sopenharmony_ci goto free_dt; 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci php_slot->dn->child = NULL; 30262306a36Sopenharmony_ci ret = of_changeset_apply(&php_slot->ocs); 30362306a36Sopenharmony_ci if (ret) { 30462306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d applying changeset\n", ret); 30562306a36Sopenharmony_ci goto destroy_changeset; 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci /* Add device node firmware data */ 30962306a36Sopenharmony_ci pnv_php_add_pdns(php_slot); 31062306a36Sopenharmony_ci php_slot->fdt = fdt; 31162306a36Sopenharmony_ci php_slot->dt = dt; 31262306a36Sopenharmony_ci kfree(fdt1); 31362306a36Sopenharmony_ci goto out; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_cidestroy_changeset: 31662306a36Sopenharmony_ci of_changeset_destroy(&php_slot->ocs); 31762306a36Sopenharmony_cifree_dt: 31862306a36Sopenharmony_ci kfree(dt); 31962306a36Sopenharmony_ci php_slot->dn->child = NULL; 32062306a36Sopenharmony_cifree_fdt: 32162306a36Sopenharmony_ci kfree(fdt); 32262306a36Sopenharmony_cifree_fdt1: 32362306a36Sopenharmony_ci kfree(fdt1); 32462306a36Sopenharmony_ciout: 32562306a36Sopenharmony_ci return ret; 32662306a36Sopenharmony_ci} 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cistatic inline struct pnv_php_slot *to_pnv_php_slot(struct hotplug_slot *slot) 32962306a36Sopenharmony_ci{ 33062306a36Sopenharmony_ci return container_of(slot, struct pnv_php_slot, slot); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ciint pnv_php_set_slot_power_state(struct hotplug_slot *slot, 33462306a36Sopenharmony_ci uint8_t state) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 33762306a36Sopenharmony_ci struct opal_msg msg; 33862306a36Sopenharmony_ci int ret; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci ret = pnv_pci_set_power_state(php_slot->id, state, &msg); 34162306a36Sopenharmony_ci if (ret > 0) { 34262306a36Sopenharmony_ci if (be64_to_cpu(msg.params[1]) != php_slot->dn->phandle || 34362306a36Sopenharmony_ci be64_to_cpu(msg.params[2]) != state) { 34462306a36Sopenharmony_ci SLOT_WARN(php_slot, "Wrong msg (%lld, %lld, %lld)\n", 34562306a36Sopenharmony_ci be64_to_cpu(msg.params[1]), 34662306a36Sopenharmony_ci be64_to_cpu(msg.params[2]), 34762306a36Sopenharmony_ci be64_to_cpu(msg.params[3])); 34862306a36Sopenharmony_ci return -ENOMSG; 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci if (be64_to_cpu(msg.params[3]) != OPAL_SUCCESS) { 35162306a36Sopenharmony_ci ret = -ENODEV; 35262306a36Sopenharmony_ci goto error; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci } else if (ret < 0) { 35562306a36Sopenharmony_ci goto error; 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci if (state == OPAL_PCI_SLOT_POWER_OFF || state == OPAL_PCI_SLOT_OFFLINE) 35962306a36Sopenharmony_ci pnv_php_rmv_devtree(php_slot); 36062306a36Sopenharmony_ci else 36162306a36Sopenharmony_ci ret = pnv_php_add_devtree(php_slot); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return ret; 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_cierror: 36662306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d powering %s\n", 36762306a36Sopenharmony_ci ret, (state == OPAL_PCI_SLOT_POWER_ON) ? "on" : "off"); 36862306a36Sopenharmony_ci return ret; 36962306a36Sopenharmony_ci} 37062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pnv_php_set_slot_power_state); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_cistatic int pnv_php_get_power_state(struct hotplug_slot *slot, u8 *state) 37362306a36Sopenharmony_ci{ 37462306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 37562306a36Sopenharmony_ci uint8_t power_state = OPAL_PCI_SLOT_POWER_ON; 37662306a36Sopenharmony_ci int ret; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci /* 37962306a36Sopenharmony_ci * Retrieve power status from firmware. If we fail 38062306a36Sopenharmony_ci * getting that, the power status fails back to 38162306a36Sopenharmony_ci * be on. 38262306a36Sopenharmony_ci */ 38362306a36Sopenharmony_ci ret = pnv_pci_get_power_state(php_slot->id, &power_state); 38462306a36Sopenharmony_ci if (ret) { 38562306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d getting power status\n", 38662306a36Sopenharmony_ci ret); 38762306a36Sopenharmony_ci } else { 38862306a36Sopenharmony_ci *state = power_state; 38962306a36Sopenharmony_ci } 39062306a36Sopenharmony_ci 39162306a36Sopenharmony_ci return 0; 39262306a36Sopenharmony_ci} 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_cistatic int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state) 39562306a36Sopenharmony_ci{ 39662306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 39762306a36Sopenharmony_ci uint8_t presence = OPAL_PCI_SLOT_EMPTY; 39862306a36Sopenharmony_ci int ret; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* 40162306a36Sopenharmony_ci * Retrieve presence status from firmware. If we can't 40262306a36Sopenharmony_ci * get that, it will fail back to be empty. 40362306a36Sopenharmony_ci */ 40462306a36Sopenharmony_ci ret = pnv_pci_get_presence_state(php_slot->id, &presence); 40562306a36Sopenharmony_ci if (ret >= 0) { 40662306a36Sopenharmony_ci *state = presence; 40762306a36Sopenharmony_ci ret = 0; 40862306a36Sopenharmony_ci } else { 40962306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d getting presence\n", ret); 41062306a36Sopenharmony_ci } 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci return ret; 41362306a36Sopenharmony_ci} 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_cistatic int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state) 41662306a36Sopenharmony_ci{ 41762306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci *state = php_slot->attention_state; 42062306a36Sopenharmony_ci return 0; 42162306a36Sopenharmony_ci} 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_cistatic int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state) 42462306a36Sopenharmony_ci{ 42562306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 42662306a36Sopenharmony_ci struct pci_dev *bridge = php_slot->pdev; 42762306a36Sopenharmony_ci u16 new, mask; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci php_slot->attention_state = state; 43062306a36Sopenharmony_ci if (!bridge) 43162306a36Sopenharmony_ci return 0; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci mask = PCI_EXP_SLTCTL_AIC; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (state) 43662306a36Sopenharmony_ci new = PCI_EXP_SLTCTL_ATTN_IND_ON; 43762306a36Sopenharmony_ci else 43862306a36Sopenharmony_ci new = PCI_EXP_SLTCTL_ATTN_IND_OFF; 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci pcie_capability_clear_and_set_word(bridge, PCI_EXP_SLTCTL, mask, new); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci return 0; 44362306a36Sopenharmony_ci} 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_cistatic int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan) 44662306a36Sopenharmony_ci{ 44762306a36Sopenharmony_ci struct hotplug_slot *slot = &php_slot->slot; 44862306a36Sopenharmony_ci uint8_t presence = OPAL_PCI_SLOT_EMPTY; 44962306a36Sopenharmony_ci uint8_t power_status = OPAL_PCI_SLOT_POWER_ON; 45062306a36Sopenharmony_ci int ret; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci /* Check if the slot has been configured */ 45362306a36Sopenharmony_ci if (php_slot->state != PNV_PHP_STATE_REGISTERED) 45462306a36Sopenharmony_ci return 0; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci /* Retrieve slot presence status */ 45762306a36Sopenharmony_ci ret = pnv_php_get_adapter_state(slot, &presence); 45862306a36Sopenharmony_ci if (ret) 45962306a36Sopenharmony_ci return ret; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci /* 46262306a36Sopenharmony_ci * Proceed if there have nothing behind the slot. However, 46362306a36Sopenharmony_ci * we should leave the slot in registered state at the 46462306a36Sopenharmony_ci * beginning. Otherwise, the PCI devices inserted afterwards 46562306a36Sopenharmony_ci * won't be probed and populated. 46662306a36Sopenharmony_ci */ 46762306a36Sopenharmony_ci if (presence == OPAL_PCI_SLOT_EMPTY) { 46862306a36Sopenharmony_ci if (!php_slot->power_state_check) { 46962306a36Sopenharmony_ci php_slot->power_state_check = true; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci return 0; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci goto scan; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci /* 47862306a36Sopenharmony_ci * If the power supply to the slot is off, we can't detect 47962306a36Sopenharmony_ci * adapter presence state. That means we have to turn the 48062306a36Sopenharmony_ci * slot on before going to probe slot's presence state. 48162306a36Sopenharmony_ci * 48262306a36Sopenharmony_ci * On the first time, we don't change the power status to 48362306a36Sopenharmony_ci * boost system boot with assumption that the firmware 48462306a36Sopenharmony_ci * supplies consistent slot power status: empty slot always 48562306a36Sopenharmony_ci * has its power off and non-empty slot has its power on. 48662306a36Sopenharmony_ci */ 48762306a36Sopenharmony_ci if (!php_slot->power_state_check) { 48862306a36Sopenharmony_ci php_slot->power_state_check = true; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci ret = pnv_php_get_power_state(slot, &power_status); 49162306a36Sopenharmony_ci if (ret) 49262306a36Sopenharmony_ci return ret; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci if (power_status != OPAL_PCI_SLOT_POWER_ON) 49562306a36Sopenharmony_ci return 0; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci /* Check the power status. Scan the slot if it is already on */ 49962306a36Sopenharmony_ci ret = pnv_php_get_power_state(slot, &power_status); 50062306a36Sopenharmony_ci if (ret) 50162306a36Sopenharmony_ci return ret; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci if (power_status == OPAL_PCI_SLOT_POWER_ON) 50462306a36Sopenharmony_ci goto scan; 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci /* Power is off, turn it on and then scan the slot */ 50762306a36Sopenharmony_ci ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON); 50862306a36Sopenharmony_ci if (ret) 50962306a36Sopenharmony_ci return ret; 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ciscan: 51262306a36Sopenharmony_ci if (presence == OPAL_PCI_SLOT_PRESENT) { 51362306a36Sopenharmony_ci if (rescan) { 51462306a36Sopenharmony_ci pci_lock_rescan_remove(); 51562306a36Sopenharmony_ci pci_hp_add_devices(php_slot->bus); 51662306a36Sopenharmony_ci pci_unlock_rescan_remove(); 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci /* Rescan for child hotpluggable slots */ 52062306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_POPULATED; 52162306a36Sopenharmony_ci if (rescan) 52262306a36Sopenharmony_ci pnv_php_register(php_slot->dn); 52362306a36Sopenharmony_ci } else { 52462306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_POPULATED; 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci return 0; 52862306a36Sopenharmony_ci} 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_cistatic int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 53362306a36Sopenharmony_ci struct pci_dev *bridge = php_slot->pdev; 53462306a36Sopenharmony_ci uint16_t sts; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci /* 53762306a36Sopenharmony_ci * The CAPI folks want pnv_php to drive OpenCAPI slots 53862306a36Sopenharmony_ci * which don't have a bridge. Only claim to support 53962306a36Sopenharmony_ci * reset_slot() if we have a bridge device (for now...) 54062306a36Sopenharmony_ci */ 54162306a36Sopenharmony_ci if (probe) 54262306a36Sopenharmony_ci return !bridge; 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_ci /* mask our interrupt while resetting the bridge */ 54562306a36Sopenharmony_ci if (php_slot->irq > 0) 54662306a36Sopenharmony_ci disable_irq(php_slot->irq); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci pci_bridge_secondary_bus_reset(bridge); 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci /* clear any state changes that happened due to the reset */ 55162306a36Sopenharmony_ci pcie_capability_read_word(php_slot->pdev, PCI_EXP_SLTSTA, &sts); 55262306a36Sopenharmony_ci sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); 55362306a36Sopenharmony_ci pcie_capability_write_word(php_slot->pdev, PCI_EXP_SLTSTA, sts); 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci if (php_slot->irq > 0) 55662306a36Sopenharmony_ci enable_irq(php_slot->irq); 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci return 0; 55962306a36Sopenharmony_ci} 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_cistatic int pnv_php_enable_slot(struct hotplug_slot *slot) 56262306a36Sopenharmony_ci{ 56362306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci return pnv_php_enable(php_slot, true); 56662306a36Sopenharmony_ci} 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_cistatic int pnv_php_disable_slot(struct hotplug_slot *slot) 56962306a36Sopenharmony_ci{ 57062306a36Sopenharmony_ci struct pnv_php_slot *php_slot = to_pnv_php_slot(slot); 57162306a36Sopenharmony_ci int ret; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci /* 57462306a36Sopenharmony_ci * Allow to disable a slot already in the registered state to 57562306a36Sopenharmony_ci * cover cases where the slot couldn't be enabled and never 57662306a36Sopenharmony_ci * reached the populated state 57762306a36Sopenharmony_ci */ 57862306a36Sopenharmony_ci if (php_slot->state != PNV_PHP_STATE_POPULATED && 57962306a36Sopenharmony_ci php_slot->state != PNV_PHP_STATE_REGISTERED) 58062306a36Sopenharmony_ci return 0; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci /* Remove all devices behind the slot */ 58362306a36Sopenharmony_ci pci_lock_rescan_remove(); 58462306a36Sopenharmony_ci pci_hp_remove_devices(php_slot->bus); 58562306a36Sopenharmony_ci pci_unlock_rescan_remove(); 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Detach the child hotpluggable slots */ 58862306a36Sopenharmony_ci pnv_php_unregister(php_slot->dn); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci /* Notify firmware and remove device nodes */ 59162306a36Sopenharmony_ci ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_OFF); 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_REGISTERED; 59462306a36Sopenharmony_ci return ret; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistatic const struct hotplug_slot_ops php_slot_ops = { 59862306a36Sopenharmony_ci .get_power_status = pnv_php_get_power_state, 59962306a36Sopenharmony_ci .get_adapter_status = pnv_php_get_adapter_state, 60062306a36Sopenharmony_ci .get_attention_status = pnv_php_get_attention_state, 60162306a36Sopenharmony_ci .set_attention_status = pnv_php_set_attention_state, 60262306a36Sopenharmony_ci .enable_slot = pnv_php_enable_slot, 60362306a36Sopenharmony_ci .disable_slot = pnv_php_disable_slot, 60462306a36Sopenharmony_ci .reset_slot = pnv_php_reset_slot, 60562306a36Sopenharmony_ci}; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_cistatic void pnv_php_release(struct pnv_php_slot *php_slot) 60862306a36Sopenharmony_ci{ 60962306a36Sopenharmony_ci unsigned long flags; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci /* Remove from global or child list */ 61262306a36Sopenharmony_ci spin_lock_irqsave(&pnv_php_lock, flags); 61362306a36Sopenharmony_ci list_del(&php_slot->link); 61462306a36Sopenharmony_ci spin_unlock_irqrestore(&pnv_php_lock, flags); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci /* Detach from parent */ 61762306a36Sopenharmony_ci pnv_php_put_slot(php_slot); 61862306a36Sopenharmony_ci pnv_php_put_slot(php_slot->parent); 61962306a36Sopenharmony_ci} 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_cistatic struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn) 62262306a36Sopenharmony_ci{ 62362306a36Sopenharmony_ci struct pnv_php_slot *php_slot; 62462306a36Sopenharmony_ci struct pci_bus *bus; 62562306a36Sopenharmony_ci const char *label; 62662306a36Sopenharmony_ci uint64_t id; 62762306a36Sopenharmony_ci int ret; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci ret = of_property_read_string(dn, "ibm,slot-label", &label); 63062306a36Sopenharmony_ci if (ret) 63162306a36Sopenharmony_ci return NULL; 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci if (pnv_pci_get_slot_id(dn, &id)) 63462306a36Sopenharmony_ci return NULL; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci bus = pci_find_bus_by_node(dn); 63762306a36Sopenharmony_ci if (!bus) 63862306a36Sopenharmony_ci return NULL; 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci php_slot = kzalloc(sizeof(*php_slot), GFP_KERNEL); 64162306a36Sopenharmony_ci if (!php_slot) 64262306a36Sopenharmony_ci return NULL; 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci php_slot->name = kstrdup(label, GFP_KERNEL); 64562306a36Sopenharmony_ci if (!php_slot->name) { 64662306a36Sopenharmony_ci kfree(php_slot); 64762306a36Sopenharmony_ci return NULL; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci if (dn->child && PCI_DN(dn->child)) 65162306a36Sopenharmony_ci php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn); 65262306a36Sopenharmony_ci else 65362306a36Sopenharmony_ci php_slot->slot_no = -1; /* Placeholder slot */ 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci kref_init(&php_slot->kref); 65662306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_INITIALIZED; 65762306a36Sopenharmony_ci php_slot->dn = dn; 65862306a36Sopenharmony_ci php_slot->pdev = bus->self; 65962306a36Sopenharmony_ci php_slot->bus = bus; 66062306a36Sopenharmony_ci php_slot->id = id; 66162306a36Sopenharmony_ci php_slot->power_state_check = false; 66262306a36Sopenharmony_ci php_slot->slot.ops = &php_slot_ops; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci INIT_LIST_HEAD(&php_slot->children); 66562306a36Sopenharmony_ci INIT_LIST_HEAD(&php_slot->link); 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return php_slot; 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic int pnv_php_register_slot(struct pnv_php_slot *php_slot) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct pnv_php_slot *parent; 67362306a36Sopenharmony_ci struct device_node *dn = php_slot->dn; 67462306a36Sopenharmony_ci unsigned long flags; 67562306a36Sopenharmony_ci int ret; 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci /* Check if the slot is registered or not */ 67862306a36Sopenharmony_ci parent = pnv_php_find_slot(php_slot->dn); 67962306a36Sopenharmony_ci if (parent) { 68062306a36Sopenharmony_ci pnv_php_put_slot(parent); 68162306a36Sopenharmony_ci return -EEXIST; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci /* Register PCI slot */ 68562306a36Sopenharmony_ci ret = pci_hp_register(&php_slot->slot, php_slot->bus, 68662306a36Sopenharmony_ci php_slot->slot_no, php_slot->name); 68762306a36Sopenharmony_ci if (ret) { 68862306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d registering slot\n", ret); 68962306a36Sopenharmony_ci return ret; 69062306a36Sopenharmony_ci } 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci /* Attach to the parent's child list or global list */ 69362306a36Sopenharmony_ci while ((dn = of_get_parent(dn))) { 69462306a36Sopenharmony_ci if (!PCI_DN(dn)) { 69562306a36Sopenharmony_ci of_node_put(dn); 69662306a36Sopenharmony_ci break; 69762306a36Sopenharmony_ci } 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci parent = pnv_php_find_slot(dn); 70062306a36Sopenharmony_ci if (parent) { 70162306a36Sopenharmony_ci of_node_put(dn); 70262306a36Sopenharmony_ci break; 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci of_node_put(dn); 70662306a36Sopenharmony_ci } 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci spin_lock_irqsave(&pnv_php_lock, flags); 70962306a36Sopenharmony_ci php_slot->parent = parent; 71062306a36Sopenharmony_ci if (parent) 71162306a36Sopenharmony_ci list_add_tail(&php_slot->link, &parent->children); 71262306a36Sopenharmony_ci else 71362306a36Sopenharmony_ci list_add_tail(&php_slot->link, &pnv_php_slot_list); 71462306a36Sopenharmony_ci spin_unlock_irqrestore(&pnv_php_lock, flags); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_REGISTERED; 71762306a36Sopenharmony_ci return 0; 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_cistatic int pnv_php_enable_msix(struct pnv_php_slot *php_slot) 72162306a36Sopenharmony_ci{ 72262306a36Sopenharmony_ci struct pci_dev *pdev = php_slot->pdev; 72362306a36Sopenharmony_ci struct msix_entry entry; 72462306a36Sopenharmony_ci int nr_entries, ret; 72562306a36Sopenharmony_ci u16 pcie_flag; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci /* Get total number of MSIx entries */ 72862306a36Sopenharmony_ci nr_entries = pci_msix_vec_count(pdev); 72962306a36Sopenharmony_ci if (nr_entries < 0) 73062306a36Sopenharmony_ci return nr_entries; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci /* Check hotplug MSIx entry is in range */ 73362306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_FLAGS, &pcie_flag); 73462306a36Sopenharmony_ci entry.entry = (pcie_flag & PCI_EXP_FLAGS_IRQ) >> 9; 73562306a36Sopenharmony_ci if (entry.entry >= nr_entries) 73662306a36Sopenharmony_ci return -ERANGE; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci /* Enable MSIx */ 73962306a36Sopenharmony_ci ret = pci_enable_msix_exact(pdev, &entry, 1); 74062306a36Sopenharmony_ci if (ret) { 74162306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d enabling MSIx\n", ret); 74262306a36Sopenharmony_ci return ret; 74362306a36Sopenharmony_ci } 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci return entry.vector; 74662306a36Sopenharmony_ci} 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_cistatic void pnv_php_event_handler(struct work_struct *work) 74962306a36Sopenharmony_ci{ 75062306a36Sopenharmony_ci struct pnv_php_event *event = 75162306a36Sopenharmony_ci container_of(work, struct pnv_php_event, work); 75262306a36Sopenharmony_ci struct pnv_php_slot *php_slot = event->php_slot; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci if (event->added) 75562306a36Sopenharmony_ci pnv_php_enable_slot(&php_slot->slot); 75662306a36Sopenharmony_ci else 75762306a36Sopenharmony_ci pnv_php_disable_slot(&php_slot->slot); 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci kfree(event); 76062306a36Sopenharmony_ci} 76162306a36Sopenharmony_ci 76262306a36Sopenharmony_cistatic irqreturn_t pnv_php_interrupt(int irq, void *data) 76362306a36Sopenharmony_ci{ 76462306a36Sopenharmony_ci struct pnv_php_slot *php_slot = data; 76562306a36Sopenharmony_ci struct pci_dev *pchild, *pdev = php_slot->pdev; 76662306a36Sopenharmony_ci struct eeh_dev *edev; 76762306a36Sopenharmony_ci struct eeh_pe *pe; 76862306a36Sopenharmony_ci struct pnv_php_event *event; 76962306a36Sopenharmony_ci u16 sts, lsts; 77062306a36Sopenharmony_ci u8 presence; 77162306a36Sopenharmony_ci bool added; 77262306a36Sopenharmony_ci unsigned long flags; 77362306a36Sopenharmony_ci int ret; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts); 77662306a36Sopenharmony_ci sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); 77762306a36Sopenharmony_ci pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts); 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci pci_dbg(pdev, "PCI slot [%s]: HP int! DLAct: %d, PresDet: %d\n", 78062306a36Sopenharmony_ci php_slot->name, 78162306a36Sopenharmony_ci !!(sts & PCI_EXP_SLTSTA_DLLSC), 78262306a36Sopenharmony_ci !!(sts & PCI_EXP_SLTSTA_PDC)); 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci if (sts & PCI_EXP_SLTSTA_DLLSC) { 78562306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lsts); 78662306a36Sopenharmony_ci added = !!(lsts & PCI_EXP_LNKSTA_DLLLA); 78762306a36Sopenharmony_ci } else if (!(php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) && 78862306a36Sopenharmony_ci (sts & PCI_EXP_SLTSTA_PDC)) { 78962306a36Sopenharmony_ci ret = pnv_pci_get_presence_state(php_slot->id, &presence); 79062306a36Sopenharmony_ci if (ret) { 79162306a36Sopenharmony_ci SLOT_WARN(php_slot, 79262306a36Sopenharmony_ci "PCI slot [%s] error %d getting presence (0x%04x), to retry the operation.\n", 79362306a36Sopenharmony_ci php_slot->name, ret, sts); 79462306a36Sopenharmony_ci return IRQ_HANDLED; 79562306a36Sopenharmony_ci } 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci added = !!(presence == OPAL_PCI_SLOT_PRESENT); 79862306a36Sopenharmony_ci } else { 79962306a36Sopenharmony_ci pci_dbg(pdev, "PCI slot [%s]: Spurious IRQ?\n", php_slot->name); 80062306a36Sopenharmony_ci return IRQ_NONE; 80162306a36Sopenharmony_ci } 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci /* Freeze the removed PE to avoid unexpected error reporting */ 80462306a36Sopenharmony_ci if (!added) { 80562306a36Sopenharmony_ci pchild = list_first_entry_or_null(&php_slot->bus->devices, 80662306a36Sopenharmony_ci struct pci_dev, bus_list); 80762306a36Sopenharmony_ci edev = pchild ? pci_dev_to_eeh_dev(pchild) : NULL; 80862306a36Sopenharmony_ci pe = edev ? edev->pe : NULL; 80962306a36Sopenharmony_ci if (pe) { 81062306a36Sopenharmony_ci eeh_serialize_lock(&flags); 81162306a36Sopenharmony_ci eeh_pe_mark_isolated(pe); 81262306a36Sopenharmony_ci eeh_serialize_unlock(flags); 81362306a36Sopenharmony_ci eeh_pe_set_option(pe, EEH_OPT_FREEZE_PE); 81462306a36Sopenharmony_ci } 81562306a36Sopenharmony_ci } 81662306a36Sopenharmony_ci 81762306a36Sopenharmony_ci /* 81862306a36Sopenharmony_ci * The PE is left in frozen state if the event is missed. It's 81962306a36Sopenharmony_ci * fine as the PCI devices (PE) aren't functional any more. 82062306a36Sopenharmony_ci */ 82162306a36Sopenharmony_ci event = kzalloc(sizeof(*event), GFP_ATOMIC); 82262306a36Sopenharmony_ci if (!event) { 82362306a36Sopenharmony_ci SLOT_WARN(php_slot, 82462306a36Sopenharmony_ci "PCI slot [%s] missed hotplug event 0x%04x\n", 82562306a36Sopenharmony_ci php_slot->name, sts); 82662306a36Sopenharmony_ci return IRQ_HANDLED; 82762306a36Sopenharmony_ci } 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci pci_info(pdev, "PCI slot [%s] %s (IRQ: %d)\n", 83062306a36Sopenharmony_ci php_slot->name, added ? "added" : "removed", irq); 83162306a36Sopenharmony_ci INIT_WORK(&event->work, pnv_php_event_handler); 83262306a36Sopenharmony_ci event->added = added; 83362306a36Sopenharmony_ci event->php_slot = php_slot; 83462306a36Sopenharmony_ci queue_work(php_slot->wq, &event->work); 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_ci return IRQ_HANDLED; 83762306a36Sopenharmony_ci} 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_cistatic void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq) 84062306a36Sopenharmony_ci{ 84162306a36Sopenharmony_ci struct pci_dev *pdev = php_slot->pdev; 84262306a36Sopenharmony_ci u32 broken_pdc = 0; 84362306a36Sopenharmony_ci u16 sts, ctrl; 84462306a36Sopenharmony_ci int ret; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci /* Allocate workqueue */ 84762306a36Sopenharmony_ci php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name); 84862306a36Sopenharmony_ci if (!php_slot->wq) { 84962306a36Sopenharmony_ci SLOT_WARN(php_slot, "Cannot alloc workqueue\n"); 85062306a36Sopenharmony_ci pnv_php_disable_irq(php_slot, true); 85162306a36Sopenharmony_ci return; 85262306a36Sopenharmony_ci } 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci /* Check PDC (Presence Detection Change) is broken or not */ 85562306a36Sopenharmony_ci ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc", 85662306a36Sopenharmony_ci &broken_pdc); 85762306a36Sopenharmony_ci if (!ret && broken_pdc) 85862306a36Sopenharmony_ci php_slot->flags |= PNV_PHP_FLAG_BROKEN_PDC; 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci /* Clear pending interrupts */ 86162306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts); 86262306a36Sopenharmony_ci if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) 86362306a36Sopenharmony_ci sts |= PCI_EXP_SLTSTA_DLLSC; 86462306a36Sopenharmony_ci else 86562306a36Sopenharmony_ci sts |= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC); 86662306a36Sopenharmony_ci pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts); 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci /* Request the interrupt */ 86962306a36Sopenharmony_ci ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED, 87062306a36Sopenharmony_ci php_slot->name, php_slot); 87162306a36Sopenharmony_ci if (ret) { 87262306a36Sopenharmony_ci pnv_php_disable_irq(php_slot, true); 87362306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq); 87462306a36Sopenharmony_ci return; 87562306a36Sopenharmony_ci } 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_ci /* Enable the interrupts */ 87862306a36Sopenharmony_ci pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl); 87962306a36Sopenharmony_ci if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) { 88062306a36Sopenharmony_ci ctrl &= ~PCI_EXP_SLTCTL_PDCE; 88162306a36Sopenharmony_ci ctrl |= (PCI_EXP_SLTCTL_HPIE | 88262306a36Sopenharmony_ci PCI_EXP_SLTCTL_DLLSCE); 88362306a36Sopenharmony_ci } else { 88462306a36Sopenharmony_ci ctrl |= (PCI_EXP_SLTCTL_HPIE | 88562306a36Sopenharmony_ci PCI_EXP_SLTCTL_PDCE | 88662306a36Sopenharmony_ci PCI_EXP_SLTCTL_DLLSCE); 88762306a36Sopenharmony_ci } 88862306a36Sopenharmony_ci pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci /* The interrupt is initialized successfully when @irq is valid */ 89162306a36Sopenharmony_ci php_slot->irq = irq; 89262306a36Sopenharmony_ci} 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_cistatic void pnv_php_enable_irq(struct pnv_php_slot *php_slot) 89562306a36Sopenharmony_ci{ 89662306a36Sopenharmony_ci struct pci_dev *pdev = php_slot->pdev; 89762306a36Sopenharmony_ci int irq, ret; 89862306a36Sopenharmony_ci 89962306a36Sopenharmony_ci /* 90062306a36Sopenharmony_ci * The MSI/MSIx interrupt might have been occupied by other 90162306a36Sopenharmony_ci * drivers. Don't populate the surprise hotplug capability 90262306a36Sopenharmony_ci * in that case. 90362306a36Sopenharmony_ci */ 90462306a36Sopenharmony_ci if (pci_dev_msi_enabled(pdev)) 90562306a36Sopenharmony_ci return; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci ret = pci_enable_device(pdev); 90862306a36Sopenharmony_ci if (ret) { 90962306a36Sopenharmony_ci SLOT_WARN(php_slot, "Error %d enabling device\n", ret); 91062306a36Sopenharmony_ci return; 91162306a36Sopenharmony_ci } 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci pci_set_master(pdev); 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci /* Enable MSIx interrupt */ 91662306a36Sopenharmony_ci irq = pnv_php_enable_msix(php_slot); 91762306a36Sopenharmony_ci if (irq > 0) { 91862306a36Sopenharmony_ci pnv_php_init_irq(php_slot, irq); 91962306a36Sopenharmony_ci return; 92062306a36Sopenharmony_ci } 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci /* 92362306a36Sopenharmony_ci * Use MSI if MSIx doesn't work. Fail back to legacy INTx 92462306a36Sopenharmony_ci * if MSI doesn't work either 92562306a36Sopenharmony_ci */ 92662306a36Sopenharmony_ci ret = pci_enable_msi(pdev); 92762306a36Sopenharmony_ci if (!ret || pdev->irq) { 92862306a36Sopenharmony_ci irq = pdev->irq; 92962306a36Sopenharmony_ci pnv_php_init_irq(php_slot, irq); 93062306a36Sopenharmony_ci } 93162306a36Sopenharmony_ci} 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_cistatic int pnv_php_register_one(struct device_node *dn) 93462306a36Sopenharmony_ci{ 93562306a36Sopenharmony_ci struct pnv_php_slot *php_slot; 93662306a36Sopenharmony_ci u32 prop32; 93762306a36Sopenharmony_ci int ret; 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci /* Check if it's hotpluggable slot */ 94062306a36Sopenharmony_ci ret = of_property_read_u32(dn, "ibm,slot-pluggable", &prop32); 94162306a36Sopenharmony_ci if (ret || !prop32) 94262306a36Sopenharmony_ci return -ENXIO; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci ret = of_property_read_u32(dn, "ibm,reset-by-firmware", &prop32); 94562306a36Sopenharmony_ci if (ret || !prop32) 94662306a36Sopenharmony_ci return -ENXIO; 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci php_slot = pnv_php_alloc_slot(dn); 94962306a36Sopenharmony_ci if (!php_slot) 95062306a36Sopenharmony_ci return -ENODEV; 95162306a36Sopenharmony_ci 95262306a36Sopenharmony_ci ret = pnv_php_register_slot(php_slot); 95362306a36Sopenharmony_ci if (ret) 95462306a36Sopenharmony_ci goto free_slot; 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci ret = pnv_php_enable(php_slot, false); 95762306a36Sopenharmony_ci if (ret) 95862306a36Sopenharmony_ci goto unregister_slot; 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci /* Enable interrupt if the slot supports surprise hotplug */ 96162306a36Sopenharmony_ci ret = of_property_read_u32(dn, "ibm,slot-surprise-pluggable", &prop32); 96262306a36Sopenharmony_ci if (!ret && prop32) 96362306a36Sopenharmony_ci pnv_php_enable_irq(php_slot); 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci return 0; 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ciunregister_slot: 96862306a36Sopenharmony_ci pnv_php_unregister_one(php_slot->dn); 96962306a36Sopenharmony_cifree_slot: 97062306a36Sopenharmony_ci pnv_php_put_slot(php_slot); 97162306a36Sopenharmony_ci return ret; 97262306a36Sopenharmony_ci} 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_cistatic void pnv_php_register(struct device_node *dn) 97562306a36Sopenharmony_ci{ 97662306a36Sopenharmony_ci struct device_node *child; 97762306a36Sopenharmony_ci 97862306a36Sopenharmony_ci /* 97962306a36Sopenharmony_ci * The parent slots should be registered before their 98062306a36Sopenharmony_ci * child slots. 98162306a36Sopenharmony_ci */ 98262306a36Sopenharmony_ci for_each_child_of_node(dn, child) { 98362306a36Sopenharmony_ci pnv_php_register_one(child); 98462306a36Sopenharmony_ci pnv_php_register(child); 98562306a36Sopenharmony_ci } 98662306a36Sopenharmony_ci} 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_cistatic void pnv_php_unregister_one(struct device_node *dn) 98962306a36Sopenharmony_ci{ 99062306a36Sopenharmony_ci struct pnv_php_slot *php_slot; 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_ci php_slot = pnv_php_find_slot(dn); 99362306a36Sopenharmony_ci if (!php_slot) 99462306a36Sopenharmony_ci return; 99562306a36Sopenharmony_ci 99662306a36Sopenharmony_ci php_slot->state = PNV_PHP_STATE_OFFLINE; 99762306a36Sopenharmony_ci pci_hp_deregister(&php_slot->slot); 99862306a36Sopenharmony_ci pnv_php_release(php_slot); 99962306a36Sopenharmony_ci pnv_php_put_slot(php_slot); 100062306a36Sopenharmony_ci} 100162306a36Sopenharmony_ci 100262306a36Sopenharmony_cistatic void pnv_php_unregister(struct device_node *dn) 100362306a36Sopenharmony_ci{ 100462306a36Sopenharmony_ci struct device_node *child; 100562306a36Sopenharmony_ci 100662306a36Sopenharmony_ci /* The child slots should go before their parent slots */ 100762306a36Sopenharmony_ci for_each_child_of_node(dn, child) { 100862306a36Sopenharmony_ci pnv_php_unregister(child); 100962306a36Sopenharmony_ci pnv_php_unregister_one(child); 101062306a36Sopenharmony_ci } 101162306a36Sopenharmony_ci} 101262306a36Sopenharmony_ci 101362306a36Sopenharmony_cistatic int __init pnv_php_init(void) 101462306a36Sopenharmony_ci{ 101562306a36Sopenharmony_ci struct device_node *dn; 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_ci pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); 101862306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") 101962306a36Sopenharmony_ci pnv_php_register(dn); 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") 102262306a36Sopenharmony_ci pnv_php_register(dn); 102362306a36Sopenharmony_ci 102462306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb") 102562306a36Sopenharmony_ci pnv_php_register_one(dn); /* slot directly under the PHB */ 102662306a36Sopenharmony_ci return 0; 102762306a36Sopenharmony_ci} 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_cistatic void __exit pnv_php_exit(void) 103062306a36Sopenharmony_ci{ 103162306a36Sopenharmony_ci struct device_node *dn; 103262306a36Sopenharmony_ci 103362306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda2-phb") 103462306a36Sopenharmony_ci pnv_php_unregister(dn); 103562306a36Sopenharmony_ci 103662306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda3-phb") 103762306a36Sopenharmony_ci pnv_php_unregister(dn); 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb") 104062306a36Sopenharmony_ci pnv_php_unregister_one(dn); /* slot directly under the PHB */ 104162306a36Sopenharmony_ci} 104262306a36Sopenharmony_ci 104362306a36Sopenharmony_cimodule_init(pnv_php_init); 104462306a36Sopenharmony_cimodule_exit(pnv_php_exit); 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_ciMODULE_VERSION(DRIVER_VERSION); 104762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 104862306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 104962306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 1050