xref: /linux/drivers/pci/hotplug/pnv_php.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1736759efSBjorn Helgaas // SPDX-License-Identifier: GPL-2.0+
266725152SGavin Shan /*
366725152SGavin Shan  * PCI Hotplug Driver for PowerPC PowerNV platform.
466725152SGavin Shan  *
566725152SGavin Shan  * Copyright Gavin Shan, IBM Corporation 2016.
666725152SGavin Shan  */
766725152SGavin Shan 
8abaaac48SIlpo Järvinen #include <linux/bitfield.h>
966725152SGavin Shan #include <linux/libfdt.h>
1066725152SGavin Shan #include <linux/module.h>
1166725152SGavin Shan #include <linux/pci.h>
1266725152SGavin Shan #include <linux/pci_hotplug.h>
13b2851926SChristophe Leroy #include <linux/of_fdt.h>
1466725152SGavin Shan 
1566725152SGavin Shan #include <asm/opal.h>
1666725152SGavin Shan #include <asm/pnv-pci.h>
1766725152SGavin Shan #include <asm/ppc-pci.h>
1866725152SGavin Shan 
1966725152SGavin Shan #define DRIVER_VERSION	"0.1"
2066725152SGavin Shan #define DRIVER_AUTHOR	"Gavin Shan, IBM Corporation"
2166725152SGavin Shan #define DRIVER_DESC	"PowerPC PowerNV PCI Hotplug Driver"
2266725152SGavin Shan 
23748ac391SFrederic Barrat #define SLOT_WARN(sl, x...) \
24748ac391SFrederic Barrat 	((sl)->pdev ? pci_warn((sl)->pdev, x) : dev_warn(&(sl)->bus->dev, x))
25748ac391SFrederic Barrat 
26360aebd8SGavin Shan struct pnv_php_event {
27360aebd8SGavin Shan 	bool			added;
28360aebd8SGavin Shan 	struct pnv_php_slot	*php_slot;
29360aebd8SGavin Shan 	struct work_struct	work;
30360aebd8SGavin Shan };
31360aebd8SGavin Shan 
3266725152SGavin Shan static LIST_HEAD(pnv_php_slot_list);
3366725152SGavin Shan static DEFINE_SPINLOCK(pnv_php_lock);
3466725152SGavin Shan 
3566725152SGavin Shan static void pnv_php_register(struct device_node *dn);
3666725152SGavin Shan static void pnv_php_unregister_one(struct device_node *dn);
3766725152SGavin Shan static void pnv_php_unregister(struct device_node *dn);
3866725152SGavin Shan 
pnv_php_disable_irq(struct pnv_php_slot * php_slot,bool disable_device)3949f4b08eSGavin Shan static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
4049f4b08eSGavin Shan 				bool disable_device)
41360aebd8SGavin Shan {
42360aebd8SGavin Shan 	struct pci_dev *pdev = php_slot->pdev;
43360aebd8SGavin Shan 	u16 ctrl;
44360aebd8SGavin Shan 
45360aebd8SGavin Shan 	if (php_slot->irq > 0) {
46360aebd8SGavin Shan 		pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl);
47360aebd8SGavin Shan 		ctrl &= ~(PCI_EXP_SLTCTL_HPIE |
48360aebd8SGavin Shan 			  PCI_EXP_SLTCTL_PDCE |
49360aebd8SGavin Shan 			  PCI_EXP_SLTCTL_DLLSCE);
50360aebd8SGavin Shan 		pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl);
51360aebd8SGavin Shan 
52360aebd8SGavin Shan 		free_irq(php_slot->irq, php_slot);
53360aebd8SGavin Shan 		php_slot->irq = 0;
54360aebd8SGavin Shan 	}
55360aebd8SGavin Shan 
56360aebd8SGavin Shan 	if (php_slot->wq) {
57360aebd8SGavin Shan 		destroy_workqueue(php_slot->wq);
58360aebd8SGavin Shan 		php_slot->wq = NULL;
59360aebd8SGavin Shan 	}
60360aebd8SGavin Shan 
61*335e35b7SKrishna Kumar 	if (disable_device) {
62360aebd8SGavin Shan 		if (pdev->msix_enabled)
63360aebd8SGavin Shan 			pci_disable_msix(pdev);
64360aebd8SGavin Shan 		else if (pdev->msi_enabled)
65360aebd8SGavin Shan 			pci_disable_msi(pdev);
6649f4b08eSGavin Shan 
6749f4b08eSGavin Shan 		pci_disable_device(pdev);
6849f4b08eSGavin Shan 	}
69360aebd8SGavin Shan }
70360aebd8SGavin Shan 
pnv_php_free_slot(struct kref * kref)7166725152SGavin Shan static void pnv_php_free_slot(struct kref *kref)
7266725152SGavin Shan {
7366725152SGavin Shan 	struct pnv_php_slot *php_slot = container_of(kref,
7466725152SGavin Shan 					struct pnv_php_slot, kref);
7566725152SGavin Shan 
7666725152SGavin Shan 	WARN_ON(!list_empty(&php_slot->children));
7749f4b08eSGavin Shan 	pnv_php_disable_irq(php_slot, false);
7866725152SGavin Shan 	kfree(php_slot->name);
7966725152SGavin Shan 	kfree(php_slot);
8066725152SGavin Shan }
8166725152SGavin Shan 
pnv_php_put_slot(struct pnv_php_slot * php_slot)8266725152SGavin Shan static inline void pnv_php_put_slot(struct pnv_php_slot *php_slot)
8366725152SGavin Shan {
8466725152SGavin Shan 
8536c7c9daSGavin Shan 	if (!php_slot)
8666725152SGavin Shan 		return;
8766725152SGavin Shan 
8866725152SGavin Shan 	kref_put(&php_slot->kref, pnv_php_free_slot);
8966725152SGavin Shan }
9066725152SGavin Shan 
pnv_php_match(struct device_node * dn,struct pnv_php_slot * php_slot)9166725152SGavin Shan static struct pnv_php_slot *pnv_php_match(struct device_node *dn,
9266725152SGavin Shan 					  struct pnv_php_slot *php_slot)
9366725152SGavin Shan {
9466725152SGavin Shan 	struct pnv_php_slot *target, *tmp;
9566725152SGavin Shan 
9666725152SGavin Shan 	if (php_slot->dn == dn) {
9766725152SGavin Shan 		kref_get(&php_slot->kref);
9866725152SGavin Shan 		return php_slot;
9966725152SGavin Shan 	}
10066725152SGavin Shan 
10166725152SGavin Shan 	list_for_each_entry(tmp, &php_slot->children, link) {
10266725152SGavin Shan 		target = pnv_php_match(dn, tmp);
10366725152SGavin Shan 		if (target)
10466725152SGavin Shan 			return target;
10566725152SGavin Shan 	}
10666725152SGavin Shan 
10766725152SGavin Shan 	return NULL;
10866725152SGavin Shan }
10966725152SGavin Shan 
pnv_php_find_slot(struct device_node * dn)11089379f16SAndrew Donnellan struct pnv_php_slot *pnv_php_find_slot(struct device_node *dn)
11166725152SGavin Shan {
11266725152SGavin Shan 	struct pnv_php_slot *php_slot, *tmp;
11366725152SGavin Shan 	unsigned long flags;
11466725152SGavin Shan 
11566725152SGavin Shan 	spin_lock_irqsave(&pnv_php_lock, flags);
11666725152SGavin Shan 	list_for_each_entry(tmp, &pnv_php_slot_list, link) {
11766725152SGavin Shan 		php_slot = pnv_php_match(dn, tmp);
11866725152SGavin Shan 		if (php_slot) {
11966725152SGavin Shan 			spin_unlock_irqrestore(&pnv_php_lock, flags);
12066725152SGavin Shan 			return php_slot;
12166725152SGavin Shan 		}
12266725152SGavin Shan 	}
12366725152SGavin Shan 	spin_unlock_irqrestore(&pnv_php_lock, flags);
12466725152SGavin Shan 
12566725152SGavin Shan 	return NULL;
12666725152SGavin Shan }
12789379f16SAndrew Donnellan EXPORT_SYMBOL_GPL(pnv_php_find_slot);
12866725152SGavin Shan 
12966725152SGavin Shan /*
13066725152SGavin Shan  * Remove pdn for all children of the indicated device node.
13166725152SGavin Shan  * The function should remove pdn in a depth-first manner.
13266725152SGavin Shan  */
pnv_php_rmv_pdns(struct device_node * dn)13366725152SGavin Shan static void pnv_php_rmv_pdns(struct device_node *dn)
13466725152SGavin Shan {
13566725152SGavin Shan 	struct device_node *child;
13666725152SGavin Shan 
13766725152SGavin Shan 	for_each_child_of_node(dn, child) {
13866725152SGavin Shan 		pnv_php_rmv_pdns(child);
13966725152SGavin Shan 
14066725152SGavin Shan 		pci_remove_device_node_info(child);
14166725152SGavin Shan 	}
14266725152SGavin Shan }
14366725152SGavin Shan 
14466725152SGavin Shan /*
14566725152SGavin Shan  * Detach all child nodes of the indicated device nodes. The
14666725152SGavin Shan  * function should handle device nodes in depth-first manner.
14766725152SGavin Shan  *
14866725152SGavin Shan  * We should not invoke of_node_release() as the memory for
14966725152SGavin Shan  * individual device node is part of large memory block. The
15066725152SGavin Shan  * large block is allocated from memblock (system bootup) or
15166725152SGavin Shan  * kmalloc() when unflattening the device tree by OF changeset.
15266725152SGavin Shan  * We can not free the large block allocated from memblock. For
15366725152SGavin Shan  * later case, it should be released at once.
15466725152SGavin Shan  */
pnv_php_detach_device_nodes(struct device_node * parent)15566725152SGavin Shan static void pnv_php_detach_device_nodes(struct device_node *parent)
15666725152SGavin Shan {
15766725152SGavin Shan 	struct device_node *dn;
15866725152SGavin Shan 
15966725152SGavin Shan 	for_each_child_of_node(parent, dn) {
16066725152SGavin Shan 		pnv_php_detach_device_nodes(dn);
16166725152SGavin Shan 
16266725152SGavin Shan 		of_node_put(dn);
16366725152SGavin Shan 		of_detach_node(dn);
16466725152SGavin Shan 	}
16566725152SGavin Shan }
16666725152SGavin Shan 
pnv_php_rmv_devtree(struct pnv_php_slot * php_slot)16766725152SGavin Shan static void pnv_php_rmv_devtree(struct pnv_php_slot *php_slot)
16866725152SGavin Shan {
16966725152SGavin Shan 	pnv_php_rmv_pdns(php_slot->dn);
17066725152SGavin Shan 
17166725152SGavin Shan 	/*
17266725152SGavin Shan 	 * Decrease the refcount if the device nodes were created
17366725152SGavin Shan 	 * through OF changeset before detaching them.
17466725152SGavin Shan 	 */
17566725152SGavin Shan 	if (php_slot->fdt)
17666725152SGavin Shan 		of_changeset_destroy(&php_slot->ocs);
17766725152SGavin Shan 	pnv_php_detach_device_nodes(php_slot->dn);
17866725152SGavin Shan 
17966725152SGavin Shan 	if (php_slot->fdt) {
18066725152SGavin Shan 		kfree(php_slot->dt);
18166725152SGavin Shan 		kfree(php_slot->fdt);
18266725152SGavin Shan 		php_slot->dt        = NULL;
18366725152SGavin Shan 		php_slot->dn->child = NULL;
18466725152SGavin Shan 		php_slot->fdt       = NULL;
18566725152SGavin Shan 	}
18666725152SGavin Shan }
18766725152SGavin Shan 
18866725152SGavin Shan /*
18966725152SGavin Shan  * As the nodes in OF changeset are applied in reverse order, we
19066725152SGavin Shan  * need revert the nodes in advance so that we have correct node
19166725152SGavin Shan  * order after the changeset is applied.
19266725152SGavin Shan  */
pnv_php_reverse_nodes(struct device_node * parent)19366725152SGavin Shan static void pnv_php_reverse_nodes(struct device_node *parent)
19466725152SGavin Shan {
19566725152SGavin Shan 	struct device_node *child, *next;
19666725152SGavin Shan 
19766725152SGavin Shan 	/* In-depth first */
19866725152SGavin Shan 	for_each_child_of_node(parent, child)
19966725152SGavin Shan 		pnv_php_reverse_nodes(child);
20066725152SGavin Shan 
20166725152SGavin Shan 	/* Reverse the nodes in the child list */
20266725152SGavin Shan 	child = parent->child;
20366725152SGavin Shan 	parent->child = NULL;
20466725152SGavin Shan 	while (child) {
20566725152SGavin Shan 		next = child->sibling;
20666725152SGavin Shan 
20766725152SGavin Shan 		child->sibling = parent->child;
20866725152SGavin Shan 		parent->child = child;
20966725152SGavin Shan 		child = next;
21066725152SGavin Shan 	}
21166725152SGavin Shan }
21266725152SGavin Shan 
pnv_php_populate_changeset(struct of_changeset * ocs,struct device_node * dn)21366725152SGavin Shan static int pnv_php_populate_changeset(struct of_changeset *ocs,
21466725152SGavin Shan 				      struct device_node *dn)
21566725152SGavin Shan {
21666725152SGavin Shan 	struct device_node *child;
21766725152SGavin Shan 	int ret = 0;
21866725152SGavin Shan 
21966725152SGavin Shan 	for_each_child_of_node(dn, child) {
22066725152SGavin Shan 		ret = of_changeset_attach_node(ocs, child);
2215d9c6b8aSJulia Lawall 		if (ret) {
2225d9c6b8aSJulia Lawall 			of_node_put(child);
22366725152SGavin Shan 			break;
2245d9c6b8aSJulia Lawall 		}
22566725152SGavin Shan 
22666725152SGavin Shan 		ret = pnv_php_populate_changeset(ocs, child);
2275d9c6b8aSJulia Lawall 		if (ret) {
2285d9c6b8aSJulia Lawall 			of_node_put(child);
22966725152SGavin Shan 			break;
23066725152SGavin Shan 		}
2315d9c6b8aSJulia Lawall 	}
23266725152SGavin Shan 
23366725152SGavin Shan 	return ret;
23466725152SGavin Shan }
23566725152SGavin Shan 
pnv_php_add_one_pdn(struct device_node * dn,void * data)23666725152SGavin Shan static void *pnv_php_add_one_pdn(struct device_node *dn, void *data)
23766725152SGavin Shan {
23866725152SGavin Shan 	struct pci_controller *hose = (struct pci_controller *)data;
23966725152SGavin Shan 	struct pci_dn *pdn;
24066725152SGavin Shan 
24166725152SGavin Shan 	pdn = pci_add_device_node_info(hose, dn);
242149ba66aSGavin Shan 	if (!pdn)
24366725152SGavin Shan 		return ERR_PTR(-ENOMEM);
24466725152SGavin Shan 
24566725152SGavin Shan 	return NULL;
24666725152SGavin Shan }
24766725152SGavin Shan 
pnv_php_add_pdns(struct pnv_php_slot * slot)24866725152SGavin Shan static void pnv_php_add_pdns(struct pnv_php_slot *slot)
24966725152SGavin Shan {
25066725152SGavin Shan 	struct pci_controller *hose = pci_bus_to_host(slot->bus);
25166725152SGavin Shan 
25266725152SGavin Shan 	pci_traverse_device_nodes(slot->dn, pnv_php_add_one_pdn, hose);
25366725152SGavin Shan }
25466725152SGavin Shan 
pnv_php_add_devtree(struct pnv_php_slot * php_slot)25566725152SGavin Shan static int pnv_php_add_devtree(struct pnv_php_slot *php_slot)
25666725152SGavin Shan {
25766725152SGavin Shan 	void *fdt, *fdt1, *dt;
25866725152SGavin Shan 	int ret;
25966725152SGavin Shan 
26066725152SGavin Shan 	/* We don't know the FDT blob size. We try to get it through
26166725152SGavin Shan 	 * maximal memory chunk and then copy it to another chunk that
26266725152SGavin Shan 	 * fits the real size.
26366725152SGavin Shan 	 */
26466725152SGavin Shan 	fdt1 = kzalloc(0x10000, GFP_KERNEL);
265149ba66aSGavin Shan 	if (!fdt1) {
26666725152SGavin Shan 		ret = -ENOMEM;
26766725152SGavin Shan 		goto out;
26866725152SGavin Shan 	}
26966725152SGavin Shan 
27066725152SGavin Shan 	ret = pnv_pci_get_device_tree(php_slot->dn->phandle, fdt1, 0x10000);
271149ba66aSGavin Shan 	if (ret) {
272748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d getting FDT blob\n", ret);
27366725152SGavin Shan 		goto free_fdt1;
27466725152SGavin Shan 	}
27566725152SGavin Shan 
27674171e9dSYueHaibing 	fdt = kmemdup(fdt1, fdt_totalsize(fdt1), GFP_KERNEL);
277149ba66aSGavin Shan 	if (!fdt) {
27866725152SGavin Shan 		ret = -ENOMEM;
27966725152SGavin Shan 		goto free_fdt1;
28066725152SGavin Shan 	}
28166725152SGavin Shan 
28266725152SGavin Shan 	/* Unflatten device tree blob */
28366725152SGavin Shan 	dt = of_fdt_unflatten_tree(fdt, php_slot->dn, NULL);
284149ba66aSGavin Shan 	if (!dt) {
28566725152SGavin Shan 		ret = -EINVAL;
286748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Cannot unflatten FDT\n");
28766725152SGavin Shan 		goto free_fdt;
28866725152SGavin Shan 	}
28966725152SGavin Shan 
29066725152SGavin Shan 	/* Initialize and apply the changeset */
29166725152SGavin Shan 	of_changeset_init(&php_slot->ocs);
29266725152SGavin Shan 	pnv_php_reverse_nodes(php_slot->dn);
29366725152SGavin Shan 	ret = pnv_php_populate_changeset(&php_slot->ocs, php_slot->dn);
294149ba66aSGavin Shan 	if (ret) {
29566725152SGavin Shan 		pnv_php_reverse_nodes(php_slot->dn);
296748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d populating changeset\n",
29766725152SGavin Shan 			  ret);
29866725152SGavin Shan 		goto free_dt;
29966725152SGavin Shan 	}
30066725152SGavin Shan 
30166725152SGavin Shan 	php_slot->dn->child = NULL;
30266725152SGavin Shan 	ret = of_changeset_apply(&php_slot->ocs);
303149ba66aSGavin Shan 	if (ret) {
304748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d applying changeset\n", ret);
30566725152SGavin Shan 		goto destroy_changeset;
30666725152SGavin Shan 	}
30766725152SGavin Shan 
30866725152SGavin Shan 	/* Add device node firmware data */
30966725152SGavin Shan 	pnv_php_add_pdns(php_slot);
31066725152SGavin Shan 	php_slot->fdt = fdt;
31166725152SGavin Shan 	php_slot->dt  = dt;
31266725152SGavin Shan 	kfree(fdt1);
31366725152SGavin Shan 	goto out;
31466725152SGavin Shan 
31566725152SGavin Shan destroy_changeset:
31666725152SGavin Shan 	of_changeset_destroy(&php_slot->ocs);
31766725152SGavin Shan free_dt:
31866725152SGavin Shan 	kfree(dt);
31966725152SGavin Shan 	php_slot->dn->child = NULL;
32066725152SGavin Shan free_fdt:
32166725152SGavin Shan 	kfree(fdt);
32266725152SGavin Shan free_fdt1:
32366725152SGavin Shan 	kfree(fdt1);
32466725152SGavin Shan out:
32566725152SGavin Shan 	return ret;
32666725152SGavin Shan }
32766725152SGavin Shan 
to_pnv_php_slot(struct hotplug_slot * slot)328a7da2161SLukas Wunner static inline struct pnv_php_slot *to_pnv_php_slot(struct hotplug_slot *slot)
329a7da2161SLukas Wunner {
330a7da2161SLukas Wunner 	return container_of(slot, struct pnv_php_slot, slot);
331a7da2161SLukas Wunner }
332a7da2161SLukas Wunner 
pnv_php_set_slot_power_state(struct hotplug_slot * slot,uint8_t state)33389379f16SAndrew Donnellan int pnv_php_set_slot_power_state(struct hotplug_slot *slot,
33466725152SGavin Shan 				 uint8_t state)
33566725152SGavin Shan {
336125450f8SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
33766725152SGavin Shan 	struct opal_msg msg;
33866725152SGavin Shan 	int ret;
33966725152SGavin Shan 
34066725152SGavin Shan 	ret = pnv_pci_set_power_state(php_slot->id, state, &msg);
341149ba66aSGavin Shan 	if (ret > 0) {
34266725152SGavin Shan 		if (be64_to_cpu(msg.params[1]) != php_slot->dn->phandle	||
343323c2a26SFrederic Barrat 		    be64_to_cpu(msg.params[2]) != state) {
344748ac391SFrederic Barrat 			SLOT_WARN(php_slot, "Wrong msg (%lld, %lld, %lld)\n",
34566725152SGavin Shan 				  be64_to_cpu(msg.params[1]),
34666725152SGavin Shan 				  be64_to_cpu(msg.params[2]),
34766725152SGavin Shan 				  be64_to_cpu(msg.params[3]));
34866725152SGavin Shan 			return -ENOMSG;
34966725152SGavin Shan 		}
350323c2a26SFrederic Barrat 		if (be64_to_cpu(msg.params[3]) != OPAL_SUCCESS) {
351323c2a26SFrederic Barrat 			ret = -ENODEV;
352323c2a26SFrederic Barrat 			goto error;
353323c2a26SFrederic Barrat 		}
354149ba66aSGavin Shan 	} else if (ret < 0) {
355323c2a26SFrederic Barrat 		goto error;
35666725152SGavin Shan 	}
35766725152SGavin Shan 
3585473a6bfSAndrew Donnellan 	if (state == OPAL_PCI_SLOT_POWER_OFF || state == OPAL_PCI_SLOT_OFFLINE)
35966725152SGavin Shan 		pnv_php_rmv_devtree(php_slot);
36066725152SGavin Shan 	else
36166725152SGavin Shan 		ret = pnv_php_add_devtree(php_slot);
36266725152SGavin Shan 
36366725152SGavin Shan 	return ret;
364323c2a26SFrederic Barrat 
365323c2a26SFrederic Barrat error:
366748ac391SFrederic Barrat 	SLOT_WARN(php_slot, "Error %d powering %s\n",
367323c2a26SFrederic Barrat 		  ret, (state == OPAL_PCI_SLOT_POWER_ON) ? "on" : "off");
368323c2a26SFrederic Barrat 	return ret;
36966725152SGavin Shan }
37089379f16SAndrew Donnellan EXPORT_SYMBOL_GPL(pnv_php_set_slot_power_state);
37166725152SGavin Shan 
pnv_php_get_power_state(struct hotplug_slot * slot,u8 * state)37266725152SGavin Shan static int pnv_php_get_power_state(struct hotplug_slot *slot, u8 *state)
37366725152SGavin Shan {
374125450f8SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
37566725152SGavin Shan 	uint8_t power_state = OPAL_PCI_SLOT_POWER_ON;
37666725152SGavin Shan 	int ret;
37766725152SGavin Shan 
37866725152SGavin Shan 	/*
37966725152SGavin Shan 	 * Retrieve power status from firmware. If we fail
38066725152SGavin Shan 	 * getting that, the power status fails back to
38166725152SGavin Shan 	 * be on.
38266725152SGavin Shan 	 */
38366725152SGavin Shan 	ret = pnv_pci_get_power_state(php_slot->id, &power_state);
384149ba66aSGavin Shan 	if (ret) {
385748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d getting power status\n",
38666725152SGavin Shan 			  ret);
38766725152SGavin Shan 	} else {
38866725152SGavin Shan 		*state = power_state;
38966725152SGavin Shan 	}
39066725152SGavin Shan 
39166725152SGavin Shan 	return 0;
39266725152SGavin Shan }
39366725152SGavin Shan 
pnv_php_get_adapter_state(struct hotplug_slot * slot,u8 * state)39466725152SGavin Shan static int pnv_php_get_adapter_state(struct hotplug_slot *slot, u8 *state)
39566725152SGavin Shan {
396125450f8SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
39766725152SGavin Shan 	uint8_t presence = OPAL_PCI_SLOT_EMPTY;
39866725152SGavin Shan 	int ret;
39966725152SGavin Shan 
40066725152SGavin Shan 	/*
40166725152SGavin Shan 	 * Retrieve presence status from firmware. If we can't
40266725152SGavin Shan 	 * get that, it will fail back to be empty.
40366725152SGavin Shan 	 */
40466725152SGavin Shan 	ret = pnv_pci_get_presence_state(php_slot->id, &presence);
405149ba66aSGavin Shan 	if (ret >= 0) {
40666725152SGavin Shan 		*state = presence;
40766725152SGavin Shan 		ret = 0;
40866725152SGavin Shan 	} else {
409748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d getting presence\n", ret);
41066725152SGavin Shan 	}
41166725152SGavin Shan 
41266725152SGavin Shan 	return ret;
41366725152SGavin Shan }
41466725152SGavin Shan 
pnv_php_get_attention_state(struct hotplug_slot * slot,u8 * state)415a7da2161SLukas Wunner static int pnv_php_get_attention_state(struct hotplug_slot *slot, u8 *state)
416a7da2161SLukas Wunner {
417a7da2161SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
418a7da2161SLukas Wunner 
419a7da2161SLukas Wunner 	*state = php_slot->attention_state;
420a7da2161SLukas Wunner 	return 0;
421a7da2161SLukas Wunner }
422a7da2161SLukas Wunner 
pnv_php_set_attention_state(struct hotplug_slot * slot,u8 state)42366725152SGavin Shan static int pnv_php_set_attention_state(struct hotplug_slot *slot, u8 state)
42466725152SGavin Shan {
425a7da2161SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
426018c49e9SOliver O'Halloran 	struct pci_dev *bridge = php_slot->pdev;
427018c49e9SOliver O'Halloran 	u16 new, mask;
428a7da2161SLukas Wunner 
429a7da2161SLukas Wunner 	php_slot->attention_state = state;
430018c49e9SOliver O'Halloran 	if (!bridge)
431018c49e9SOliver O'Halloran 		return 0;
432018c49e9SOliver O'Halloran 
433018c49e9SOliver O'Halloran 	mask = PCI_EXP_SLTCTL_AIC;
434018c49e9SOliver O'Halloran 
435018c49e9SOliver O'Halloran 	if (state)
436018c49e9SOliver O'Halloran 		new = PCI_EXP_SLTCTL_ATTN_IND_ON;
437018c49e9SOliver O'Halloran 	else
438018c49e9SOliver O'Halloran 		new = PCI_EXP_SLTCTL_ATTN_IND_OFF;
439018c49e9SOliver O'Halloran 
440018c49e9SOliver O'Halloran 	pcie_capability_clear_and_set_word(bridge, PCI_EXP_SLTCTL, mask, new);
44166725152SGavin Shan 
44266725152SGavin Shan 	return 0;
44366725152SGavin Shan }
44466725152SGavin Shan 
pnv_php_enable(struct pnv_php_slot * php_slot,bool rescan)44566725152SGavin Shan static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan)
44666725152SGavin Shan {
44766725152SGavin Shan 	struct hotplug_slot *slot = &php_slot->slot;
44866725152SGavin Shan 	uint8_t presence = OPAL_PCI_SLOT_EMPTY;
44966725152SGavin Shan 	uint8_t power_status = OPAL_PCI_SLOT_POWER_ON;
45066725152SGavin Shan 	int ret;
45166725152SGavin Shan 
45266725152SGavin Shan 	/* Check if the slot has been configured */
45366725152SGavin Shan 	if (php_slot->state != PNV_PHP_STATE_REGISTERED)
45466725152SGavin Shan 		return 0;
45566725152SGavin Shan 
45666725152SGavin Shan 	/* Retrieve slot presence status */
45766725152SGavin Shan 	ret = pnv_php_get_adapter_state(slot, &presence);
458149ba66aSGavin Shan 	if (ret)
45966725152SGavin Shan 		return ret;
46066725152SGavin Shan 
461d0c42497SGavin Shan 	/*
462d0c42497SGavin Shan 	 * Proceed if there have nothing behind the slot. However,
463d0c42497SGavin Shan 	 * we should leave the slot in registered state at the
464d0c42497SGavin Shan 	 * beginning. Otherwise, the PCI devices inserted afterwards
465d0c42497SGavin Shan 	 * won't be probed and populated.
466d0c42497SGavin Shan 	 */
467d0c42497SGavin Shan 	if (presence == OPAL_PCI_SLOT_EMPTY) {
468d0c42497SGavin Shan 		if (!php_slot->power_state_check) {
469d0c42497SGavin Shan 			php_slot->power_state_check = true;
470d0c42497SGavin Shan 
471d0c42497SGavin Shan 			return 0;
472d0c42497SGavin Shan 		}
473d0c42497SGavin Shan 
47466725152SGavin Shan 		goto scan;
475d0c42497SGavin Shan 	}
47666725152SGavin Shan 
47766725152SGavin Shan 	/*
47866725152SGavin Shan 	 * If the power supply to the slot is off, we can't detect
47966725152SGavin Shan 	 * adapter presence state. That means we have to turn the
48066725152SGavin Shan 	 * slot on before going to probe slot's presence state.
48166725152SGavin Shan 	 *
48266725152SGavin Shan 	 * On the first time, we don't change the power status to
48366725152SGavin Shan 	 * boost system boot with assumption that the firmware
48466725152SGavin Shan 	 * supplies consistent slot power status: empty slot always
48566725152SGavin Shan 	 * has its power off and non-empty slot has its power on.
48666725152SGavin Shan 	 */
48766725152SGavin Shan 	if (!php_slot->power_state_check) {
48866725152SGavin Shan 		php_slot->power_state_check = true;
48966725152SGavin Shan 
49066725152SGavin Shan 		ret = pnv_php_get_power_state(slot, &power_status);
491149ba66aSGavin Shan 		if (ret)
49266725152SGavin Shan 			return ret;
49366725152SGavin Shan 
49466725152SGavin Shan 		if (power_status != OPAL_PCI_SLOT_POWER_ON)
49566725152SGavin Shan 			return 0;
49666725152SGavin Shan 	}
49766725152SGavin Shan 
49866725152SGavin Shan 	/* Check the power status. Scan the slot if it is already on */
49966725152SGavin Shan 	ret = pnv_php_get_power_state(slot, &power_status);
500149ba66aSGavin Shan 	if (ret)
50166725152SGavin Shan 		return ret;
50266725152SGavin Shan 
50366725152SGavin Shan 	if (power_status == OPAL_PCI_SLOT_POWER_ON)
50466725152SGavin Shan 		goto scan;
50566725152SGavin Shan 
50666725152SGavin Shan 	/* Power is off, turn it on and then scan the slot */
50766725152SGavin Shan 	ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_ON);
508149ba66aSGavin Shan 	if (ret)
50966725152SGavin Shan 		return ret;
51066725152SGavin Shan 
51166725152SGavin Shan scan:
51266725152SGavin Shan 	if (presence == OPAL_PCI_SLOT_PRESENT) {
51366725152SGavin Shan 		if (rescan) {
51466725152SGavin Shan 			pci_lock_rescan_remove();
51566725152SGavin Shan 			pci_hp_add_devices(php_slot->bus);
51666725152SGavin Shan 			pci_unlock_rescan_remove();
51766725152SGavin Shan 		}
51866725152SGavin Shan 
51966725152SGavin Shan 		/* Rescan for child hotpluggable slots */
52066725152SGavin Shan 		php_slot->state = PNV_PHP_STATE_POPULATED;
52166725152SGavin Shan 		if (rescan)
52266725152SGavin Shan 			pnv_php_register(php_slot->dn);
52366725152SGavin Shan 	} else {
52466725152SGavin Shan 		php_slot->state = PNV_PHP_STATE_POPULATED;
52566725152SGavin Shan 	}
52666725152SGavin Shan 
52766725152SGavin Shan 	return 0;
52866725152SGavin Shan }
52966725152SGavin Shan 
pnv_php_reset_slot(struct hotplug_slot * slot,bool probe)5309bdc81ceSAmey Narkhede static int pnv_php_reset_slot(struct hotplug_slot *slot, bool probe)
5317fd1fe4eSOliver O'Halloran {
5327fd1fe4eSOliver O'Halloran 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
5337fd1fe4eSOliver O'Halloran 	struct pci_dev *bridge = php_slot->pdev;
5347fd1fe4eSOliver O'Halloran 	uint16_t sts;
5357fd1fe4eSOliver O'Halloran 
5367fd1fe4eSOliver O'Halloran 	/*
5377fd1fe4eSOliver O'Halloran 	 * The CAPI folks want pnv_php to drive OpenCAPI slots
5387fd1fe4eSOliver O'Halloran 	 * which don't have a bridge. Only claim to support
5397fd1fe4eSOliver O'Halloran 	 * reset_slot() if we have a bridge device (for now...)
5407fd1fe4eSOliver O'Halloran 	 */
5417fd1fe4eSOliver O'Halloran 	if (probe)
5427fd1fe4eSOliver O'Halloran 		return !bridge;
5437fd1fe4eSOliver O'Halloran 
5447fd1fe4eSOliver O'Halloran 	/* mask our interrupt while resetting the bridge */
5457fd1fe4eSOliver O'Halloran 	if (php_slot->irq > 0)
5467fd1fe4eSOliver O'Halloran 		disable_irq(php_slot->irq);
5477fd1fe4eSOliver O'Halloran 
5487fd1fe4eSOliver O'Halloran 	pci_bridge_secondary_bus_reset(bridge);
5497fd1fe4eSOliver O'Halloran 
5507fd1fe4eSOliver O'Halloran 	/* clear any state changes that happened due to the reset */
5517fd1fe4eSOliver O'Halloran 	pcie_capability_read_word(php_slot->pdev, PCI_EXP_SLTSTA, &sts);
5527fd1fe4eSOliver O'Halloran 	sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
5537fd1fe4eSOliver O'Halloran 	pcie_capability_write_word(php_slot->pdev, PCI_EXP_SLTSTA, sts);
5547fd1fe4eSOliver O'Halloran 
5557fd1fe4eSOliver O'Halloran 	if (php_slot->irq > 0)
5567fd1fe4eSOliver O'Halloran 		enable_irq(php_slot->irq);
5577fd1fe4eSOliver O'Halloran 
5587fd1fe4eSOliver O'Halloran 	return 0;
5597fd1fe4eSOliver O'Halloran }
5607fd1fe4eSOliver O'Halloran 
pnv_php_enable_slot(struct hotplug_slot * slot)56166725152SGavin Shan static int pnv_php_enable_slot(struct hotplug_slot *slot)
56266725152SGavin Shan {
563a7da2161SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
56466725152SGavin Shan 
56566725152SGavin Shan 	return pnv_php_enable(php_slot, true);
56666725152SGavin Shan }
56766725152SGavin Shan 
pnv_php_disable_slot(struct hotplug_slot * slot)56866725152SGavin Shan static int pnv_php_disable_slot(struct hotplug_slot *slot)
56966725152SGavin Shan {
570125450f8SLukas Wunner 	struct pnv_php_slot *php_slot = to_pnv_php_slot(slot);
57166725152SGavin Shan 	int ret;
57266725152SGavin Shan 
573be1611e0SFrederic Barrat 	/*
574be1611e0SFrederic Barrat 	 * Allow to disable a slot already in the registered state to
575be1611e0SFrederic Barrat 	 * cover cases where the slot couldn't be enabled and never
576be1611e0SFrederic Barrat 	 * reached the populated state
577be1611e0SFrederic Barrat 	 */
578be1611e0SFrederic Barrat 	if (php_slot->state != PNV_PHP_STATE_POPULATED &&
579be1611e0SFrederic Barrat 	    php_slot->state != PNV_PHP_STATE_REGISTERED)
58066725152SGavin Shan 		return 0;
58166725152SGavin Shan 
58266725152SGavin Shan 	/* Remove all devices behind the slot */
58366725152SGavin Shan 	pci_lock_rescan_remove();
58466725152SGavin Shan 	pci_hp_remove_devices(php_slot->bus);
58566725152SGavin Shan 	pci_unlock_rescan_remove();
58666725152SGavin Shan 
58766725152SGavin Shan 	/* Detach the child hotpluggable slots */
58866725152SGavin Shan 	pnv_php_unregister(php_slot->dn);
58966725152SGavin Shan 
59066725152SGavin Shan 	/* Notify firmware and remove device nodes */
59166725152SGavin Shan 	ret = pnv_php_set_slot_power_state(slot, OPAL_PCI_SLOT_POWER_OFF);
59266725152SGavin Shan 
59366725152SGavin Shan 	php_slot->state = PNV_PHP_STATE_REGISTERED;
59466725152SGavin Shan 	return ret;
59566725152SGavin Shan }
59666725152SGavin Shan 
59781c4b5bfSLukas Wunner static const struct hotplug_slot_ops php_slot_ops = {
59866725152SGavin Shan 	.get_power_status	= pnv_php_get_power_state,
59966725152SGavin Shan 	.get_adapter_status	= pnv_php_get_adapter_state,
600a7da2161SLukas Wunner 	.get_attention_status	= pnv_php_get_attention_state,
60166725152SGavin Shan 	.set_attention_status	= pnv_php_set_attention_state,
60266725152SGavin Shan 	.enable_slot		= pnv_php_enable_slot,
60366725152SGavin Shan 	.disable_slot		= pnv_php_disable_slot,
6047fd1fe4eSOliver O'Halloran 	.reset_slot		= pnv_php_reset_slot,
60566725152SGavin Shan };
60666725152SGavin Shan 
pnv_php_release(struct pnv_php_slot * php_slot)60751bbf9beSLukas Wunner static void pnv_php_release(struct pnv_php_slot *php_slot)
60866725152SGavin Shan {
60966725152SGavin Shan 	unsigned long flags;
61066725152SGavin Shan 
61166725152SGavin Shan 	/* Remove from global or child list */
61266725152SGavin Shan 	spin_lock_irqsave(&pnv_php_lock, flags);
61366725152SGavin Shan 	list_del(&php_slot->link);
61466725152SGavin Shan 	spin_unlock_irqrestore(&pnv_php_lock, flags);
61566725152SGavin Shan 
61666725152SGavin Shan 	/* Detach from parent */
61766725152SGavin Shan 	pnv_php_put_slot(php_slot);
61866725152SGavin Shan 	pnv_php_put_slot(php_slot->parent);
61966725152SGavin Shan }
62066725152SGavin Shan 
pnv_php_alloc_slot(struct device_node * dn)62166725152SGavin Shan static struct pnv_php_slot *pnv_php_alloc_slot(struct device_node *dn)
62266725152SGavin Shan {
62366725152SGavin Shan 	struct pnv_php_slot *php_slot;
62466725152SGavin Shan 	struct pci_bus *bus;
62566725152SGavin Shan 	const char *label;
62666725152SGavin Shan 	uint64_t id;
62739f0d6fbSGavin Shan 	int ret;
62866725152SGavin Shan 
62939f0d6fbSGavin Shan 	ret = of_property_read_string(dn, "ibm,slot-label", &label);
63039f0d6fbSGavin Shan 	if (ret)
63166725152SGavin Shan 		return NULL;
63266725152SGavin Shan 
63366725152SGavin Shan 	if (pnv_pci_get_slot_id(dn, &id))
63466725152SGavin Shan 		return NULL;
63566725152SGavin Shan 
63666725152SGavin Shan 	bus = pci_find_bus_by_node(dn);
637149ba66aSGavin Shan 	if (!bus)
63866725152SGavin Shan 		return NULL;
63966725152SGavin Shan 
64066725152SGavin Shan 	php_slot = kzalloc(sizeof(*php_slot), GFP_KERNEL);
641149ba66aSGavin Shan 	if (!php_slot)
64266725152SGavin Shan 		return NULL;
64366725152SGavin Shan 
64466725152SGavin Shan 	php_slot->name = kstrdup(label, GFP_KERNEL);
645149ba66aSGavin Shan 	if (!php_slot->name) {
64666725152SGavin Shan 		kfree(php_slot);
64766725152SGavin Shan 		return NULL;
64866725152SGavin Shan 	}
64966725152SGavin Shan 
650149ba66aSGavin Shan 	if (dn->child && PCI_DN(dn->child))
65166725152SGavin Shan 		php_slot->slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn);
65266725152SGavin Shan 	else
65366725152SGavin Shan 		php_slot->slot_no = -1;   /* Placeholder slot */
65466725152SGavin Shan 
65566725152SGavin Shan 	kref_init(&php_slot->kref);
65666725152SGavin Shan 	php_slot->state	                = PNV_PHP_STATE_INITIALIZED;
65766725152SGavin Shan 	php_slot->dn	                = dn;
65866725152SGavin Shan 	php_slot->pdev	                = bus->self;
65966725152SGavin Shan 	php_slot->bus	                = bus;
66066725152SGavin Shan 	php_slot->id	                = id;
66166725152SGavin Shan 	php_slot->power_state_check     = false;
66266725152SGavin Shan 	php_slot->slot.ops              = &php_slot_ops;
66366725152SGavin Shan 
66466725152SGavin Shan 	INIT_LIST_HEAD(&php_slot->children);
66566725152SGavin Shan 	INIT_LIST_HEAD(&php_slot->link);
66666725152SGavin Shan 
66766725152SGavin Shan 	return php_slot;
66866725152SGavin Shan }
66966725152SGavin Shan 
pnv_php_register_slot(struct pnv_php_slot * php_slot)67066725152SGavin Shan static int pnv_php_register_slot(struct pnv_php_slot *php_slot)
67166725152SGavin Shan {
67266725152SGavin Shan 	struct pnv_php_slot *parent;
67366725152SGavin Shan 	struct device_node *dn = php_slot->dn;
67466725152SGavin Shan 	unsigned long flags;
67566725152SGavin Shan 	int ret;
67666725152SGavin Shan 
67766725152SGavin Shan 	/* Check if the slot is registered or not */
67866725152SGavin Shan 	parent = pnv_php_find_slot(php_slot->dn);
679149ba66aSGavin Shan 	if (parent) {
68066725152SGavin Shan 		pnv_php_put_slot(parent);
68166725152SGavin Shan 		return -EEXIST;
68266725152SGavin Shan 	}
68366725152SGavin Shan 
68466725152SGavin Shan 	/* Register PCI slot */
68566725152SGavin Shan 	ret = pci_hp_register(&php_slot->slot, php_slot->bus,
68666725152SGavin Shan 			      php_slot->slot_no, php_slot->name);
687149ba66aSGavin Shan 	if (ret) {
688748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d registering slot\n", ret);
68966725152SGavin Shan 		return ret;
69066725152SGavin Shan 	}
69166725152SGavin Shan 
69266725152SGavin Shan 	/* Attach to the parent's child list or global list */
69366725152SGavin Shan 	while ((dn = of_get_parent(dn))) {
69466725152SGavin Shan 		if (!PCI_DN(dn)) {
69566725152SGavin Shan 			of_node_put(dn);
69666725152SGavin Shan 			break;
69766725152SGavin Shan 		}
69866725152SGavin Shan 
69966725152SGavin Shan 		parent = pnv_php_find_slot(dn);
70066725152SGavin Shan 		if (parent) {
70166725152SGavin Shan 			of_node_put(dn);
70266725152SGavin Shan 			break;
70366725152SGavin Shan 		}
70466725152SGavin Shan 
70566725152SGavin Shan 		of_node_put(dn);
70666725152SGavin Shan 	}
70766725152SGavin Shan 
70866725152SGavin Shan 	spin_lock_irqsave(&pnv_php_lock, flags);
70966725152SGavin Shan 	php_slot->parent = parent;
71066725152SGavin Shan 	if (parent)
71166725152SGavin Shan 		list_add_tail(&php_slot->link, &parent->children);
71266725152SGavin Shan 	else
71366725152SGavin Shan 		list_add_tail(&php_slot->link, &pnv_php_slot_list);
71466725152SGavin Shan 	spin_unlock_irqrestore(&pnv_php_lock, flags);
71566725152SGavin Shan 
71666725152SGavin Shan 	php_slot->state = PNV_PHP_STATE_REGISTERED;
71766725152SGavin Shan 	return 0;
71866725152SGavin Shan }
71966725152SGavin Shan 
pnv_php_enable_msix(struct pnv_php_slot * php_slot)720360aebd8SGavin Shan static int pnv_php_enable_msix(struct pnv_php_slot *php_slot)
721360aebd8SGavin Shan {
722360aebd8SGavin Shan 	struct pci_dev *pdev = php_slot->pdev;
723360aebd8SGavin Shan 	struct msix_entry entry;
724360aebd8SGavin Shan 	int nr_entries, ret;
725360aebd8SGavin Shan 	u16 pcie_flag;
726360aebd8SGavin Shan 
727360aebd8SGavin Shan 	/* Get total number of MSIx entries */
728360aebd8SGavin Shan 	nr_entries = pci_msix_vec_count(pdev);
729360aebd8SGavin Shan 	if (nr_entries < 0)
730360aebd8SGavin Shan 		return nr_entries;
731360aebd8SGavin Shan 
732360aebd8SGavin Shan 	/* Check hotplug MSIx entry is in range */
733360aebd8SGavin Shan 	pcie_capability_read_word(pdev, PCI_EXP_FLAGS, &pcie_flag);
734abaaac48SIlpo Järvinen 	entry.entry = FIELD_GET(PCI_EXP_FLAGS_IRQ, pcie_flag);
735360aebd8SGavin Shan 	if (entry.entry >= nr_entries)
736360aebd8SGavin Shan 		return -ERANGE;
737360aebd8SGavin Shan 
738360aebd8SGavin Shan 	/* Enable MSIx */
739360aebd8SGavin Shan 	ret = pci_enable_msix_exact(pdev, &entry, 1);
740360aebd8SGavin Shan 	if (ret) {
741748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d enabling MSIx\n", ret);
742360aebd8SGavin Shan 		return ret;
743360aebd8SGavin Shan 	}
744360aebd8SGavin Shan 
745360aebd8SGavin Shan 	return entry.vector;
746360aebd8SGavin Shan }
747360aebd8SGavin Shan 
pnv_php_event_handler(struct work_struct * work)748360aebd8SGavin Shan static void pnv_php_event_handler(struct work_struct *work)
749360aebd8SGavin Shan {
750360aebd8SGavin Shan 	struct pnv_php_event *event =
751360aebd8SGavin Shan 		container_of(work, struct pnv_php_event, work);
752360aebd8SGavin Shan 	struct pnv_php_slot *php_slot = event->php_slot;
753360aebd8SGavin Shan 
754360aebd8SGavin Shan 	if (event->added)
755360aebd8SGavin Shan 		pnv_php_enable_slot(&php_slot->slot);
756360aebd8SGavin Shan 	else
757360aebd8SGavin Shan 		pnv_php_disable_slot(&php_slot->slot);
758360aebd8SGavin Shan 
759360aebd8SGavin Shan 	kfree(event);
760360aebd8SGavin Shan }
761360aebd8SGavin Shan 
pnv_php_interrupt(int irq,void * data)762360aebd8SGavin Shan static irqreturn_t pnv_php_interrupt(int irq, void *data)
763360aebd8SGavin Shan {
764360aebd8SGavin Shan 	struct pnv_php_slot *php_slot = data;
765360aebd8SGavin Shan 	struct pci_dev *pchild, *pdev = php_slot->pdev;
766360aebd8SGavin Shan 	struct eeh_dev *edev;
767360aebd8SGavin Shan 	struct eeh_pe *pe;
768360aebd8SGavin Shan 	struct pnv_php_event *event;
769360aebd8SGavin Shan 	u16 sts, lsts;
770360aebd8SGavin Shan 	u8 presence;
771360aebd8SGavin Shan 	bool added;
772360aebd8SGavin Shan 	unsigned long flags;
773360aebd8SGavin Shan 	int ret;
774360aebd8SGavin Shan 
775360aebd8SGavin Shan 	pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts);
776360aebd8SGavin Shan 	sts &= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
777360aebd8SGavin Shan 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts);
7787fd1fe4eSOliver O'Halloran 
7797fd1fe4eSOliver O'Halloran 	pci_dbg(pdev, "PCI slot [%s]: HP int! DLAct: %d, PresDet: %d\n",
7807fd1fe4eSOliver O'Halloran 			php_slot->name,
7817fd1fe4eSOliver O'Halloran 			!!(sts & PCI_EXP_SLTSTA_DLLSC),
7827fd1fe4eSOliver O'Halloran 			!!(sts & PCI_EXP_SLTSTA_PDC));
7837fd1fe4eSOliver O'Halloran 
784360aebd8SGavin Shan 	if (sts & PCI_EXP_SLTSTA_DLLSC) {
785360aebd8SGavin Shan 		pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lsts);
786360aebd8SGavin Shan 		added = !!(lsts & PCI_EXP_LNKSTA_DLLLA);
787454593e5SGavin Shan 	} else if (!(php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) &&
788454593e5SGavin Shan 		   (sts & PCI_EXP_SLTSTA_PDC)) {
789360aebd8SGavin Shan 		ret = pnv_pci_get_presence_state(php_slot->id, &presence);
790d7d55536SGavin Shan 		if (ret) {
791748ac391SFrederic Barrat 			SLOT_WARN(php_slot,
792748ac391SFrederic Barrat 				  "PCI slot [%s] error %d getting presence (0x%04x), to retry the operation.\n",
793d7d55536SGavin Shan 				  php_slot->name, ret, sts);
794360aebd8SGavin Shan 			return IRQ_HANDLED;
795d7d55536SGavin Shan 		}
796d7d55536SGavin Shan 
797360aebd8SGavin Shan 		added = !!(presence == OPAL_PCI_SLOT_PRESENT);
798360aebd8SGavin Shan 	} else {
7997fd1fe4eSOliver O'Halloran 		pci_dbg(pdev, "PCI slot [%s]: Spurious IRQ?\n", php_slot->name);
800360aebd8SGavin Shan 		return IRQ_NONE;
801360aebd8SGavin Shan 	}
802360aebd8SGavin Shan 
803360aebd8SGavin Shan 	/* Freeze the removed PE to avoid unexpected error reporting */
804360aebd8SGavin Shan 	if (!added) {
805360aebd8SGavin Shan 		pchild = list_first_entry_or_null(&php_slot->bus->devices,
806360aebd8SGavin Shan 						  struct pci_dev, bus_list);
807360aebd8SGavin Shan 		edev = pchild ? pci_dev_to_eeh_dev(pchild) : NULL;
808360aebd8SGavin Shan 		pe = edev ? edev->pe : NULL;
809360aebd8SGavin Shan 		if (pe) {
810360aebd8SGavin Shan 			eeh_serialize_lock(&flags);
811e762bb89SSam Bobroff 			eeh_pe_mark_isolated(pe);
812360aebd8SGavin Shan 			eeh_serialize_unlock(flags);
813360aebd8SGavin Shan 			eeh_pe_set_option(pe, EEH_OPT_FREEZE_PE);
814360aebd8SGavin Shan 		}
815360aebd8SGavin Shan 	}
816360aebd8SGavin Shan 
817360aebd8SGavin Shan 	/*
818360aebd8SGavin Shan 	 * The PE is left in frozen state if the event is missed. It's
819360aebd8SGavin Shan 	 * fine as the PCI devices (PE) aren't functional any more.
820360aebd8SGavin Shan 	 */
821360aebd8SGavin Shan 	event = kzalloc(sizeof(*event), GFP_ATOMIC);
822360aebd8SGavin Shan 	if (!event) {
823748ac391SFrederic Barrat 		SLOT_WARN(php_slot,
824748ac391SFrederic Barrat 			  "PCI slot [%s] missed hotplug event 0x%04x\n",
825360aebd8SGavin Shan 			  php_slot->name, sts);
826360aebd8SGavin Shan 		return IRQ_HANDLED;
827360aebd8SGavin Shan 	}
828360aebd8SGavin Shan 
8297506dc79SFrederick Lawler 	pci_info(pdev, "PCI slot [%s] %s (IRQ: %d)\n",
830360aebd8SGavin Shan 		 php_slot->name, added ? "added" : "removed", irq);
831360aebd8SGavin Shan 	INIT_WORK(&event->work, pnv_php_event_handler);
832360aebd8SGavin Shan 	event->added = added;
833360aebd8SGavin Shan 	event->php_slot = php_slot;
834360aebd8SGavin Shan 	queue_work(php_slot->wq, &event->work);
835360aebd8SGavin Shan 
836360aebd8SGavin Shan 	return IRQ_HANDLED;
837360aebd8SGavin Shan }
838360aebd8SGavin Shan 
pnv_php_init_irq(struct pnv_php_slot * php_slot,int irq)839360aebd8SGavin Shan static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
840360aebd8SGavin Shan {
841360aebd8SGavin Shan 	struct pci_dev *pdev = php_slot->pdev;
842454593e5SGavin Shan 	u32 broken_pdc = 0;
843360aebd8SGavin Shan 	u16 sts, ctrl;
844360aebd8SGavin Shan 	int ret;
845360aebd8SGavin Shan 
846360aebd8SGavin Shan 	/* Allocate workqueue */
847360aebd8SGavin Shan 	php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
848360aebd8SGavin Shan 	if (!php_slot->wq) {
849748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Cannot alloc workqueue\n");
85049f4b08eSGavin Shan 		pnv_php_disable_irq(php_slot, true);
851360aebd8SGavin Shan 		return;
852360aebd8SGavin Shan 	}
853360aebd8SGavin Shan 
854454593e5SGavin Shan 	/* Check PDC (Presence Detection Change) is broken or not */
855454593e5SGavin Shan 	ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc",
856454593e5SGavin Shan 				   &broken_pdc);
857454593e5SGavin Shan 	if (!ret && broken_pdc)
858454593e5SGavin Shan 		php_slot->flags |= PNV_PHP_FLAG_BROKEN_PDC;
859454593e5SGavin Shan 
860360aebd8SGavin Shan 	/* Clear pending interrupts */
861360aebd8SGavin Shan 	pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts);
862454593e5SGavin Shan 	if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC)
863454593e5SGavin Shan 		sts |= PCI_EXP_SLTSTA_DLLSC;
864454593e5SGavin Shan 	else
865360aebd8SGavin Shan 		sts |= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
866360aebd8SGavin Shan 	pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts);
867360aebd8SGavin Shan 
868360aebd8SGavin Shan 	/* Request the interrupt */
869360aebd8SGavin Shan 	ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED,
870360aebd8SGavin Shan 			  php_slot->name, php_slot);
871360aebd8SGavin Shan 	if (ret) {
87249f4b08eSGavin Shan 		pnv_php_disable_irq(php_slot, true);
873748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d enabling IRQ %d\n", ret, irq);
874360aebd8SGavin Shan 		return;
875360aebd8SGavin Shan 	}
876360aebd8SGavin Shan 
877360aebd8SGavin Shan 	/* Enable the interrupts */
878360aebd8SGavin Shan 	pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl);
879454593e5SGavin Shan 	if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) {
880454593e5SGavin Shan 		ctrl &= ~PCI_EXP_SLTCTL_PDCE;
881454593e5SGavin Shan 		ctrl |= (PCI_EXP_SLTCTL_HPIE |
882454593e5SGavin Shan 			 PCI_EXP_SLTCTL_DLLSCE);
883454593e5SGavin Shan 	} else {
884360aebd8SGavin Shan 		ctrl |= (PCI_EXP_SLTCTL_HPIE |
885360aebd8SGavin Shan 			 PCI_EXP_SLTCTL_PDCE |
886360aebd8SGavin Shan 			 PCI_EXP_SLTCTL_DLLSCE);
887454593e5SGavin Shan 	}
888360aebd8SGavin Shan 	pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl);
889360aebd8SGavin Shan 
890360aebd8SGavin Shan 	/* The interrupt is initialized successfully when @irq is valid */
891360aebd8SGavin Shan 	php_slot->irq = irq;
892360aebd8SGavin Shan }
893360aebd8SGavin Shan 
pnv_php_enable_irq(struct pnv_php_slot * php_slot)894360aebd8SGavin Shan static void pnv_php_enable_irq(struct pnv_php_slot *php_slot)
895360aebd8SGavin Shan {
896360aebd8SGavin Shan 	struct pci_dev *pdev = php_slot->pdev;
897360aebd8SGavin Shan 	int irq, ret;
898360aebd8SGavin Shan 
899303529d6SGavin Shan 	/*
900303529d6SGavin Shan 	 * The MSI/MSIx interrupt might have been occupied by other
901303529d6SGavin Shan 	 * drivers. Don't populate the surprise hotplug capability
902303529d6SGavin Shan 	 * in that case.
903303529d6SGavin Shan 	 */
904303529d6SGavin Shan 	if (pci_dev_msi_enabled(pdev))
905303529d6SGavin Shan 		return;
906303529d6SGavin Shan 
907360aebd8SGavin Shan 	ret = pci_enable_device(pdev);
908360aebd8SGavin Shan 	if (ret) {
909748ac391SFrederic Barrat 		SLOT_WARN(php_slot, "Error %d enabling device\n", ret);
910360aebd8SGavin Shan 		return;
911360aebd8SGavin Shan 	}
912360aebd8SGavin Shan 
913360aebd8SGavin Shan 	pci_set_master(pdev);
914360aebd8SGavin Shan 
915360aebd8SGavin Shan 	/* Enable MSIx interrupt */
916360aebd8SGavin Shan 	irq = pnv_php_enable_msix(php_slot);
917360aebd8SGavin Shan 	if (irq > 0) {
918360aebd8SGavin Shan 		pnv_php_init_irq(php_slot, irq);
919360aebd8SGavin Shan 		return;
920360aebd8SGavin Shan 	}
921360aebd8SGavin Shan 
922360aebd8SGavin Shan 	/*
923360aebd8SGavin Shan 	 * Use MSI if MSIx doesn't work. Fail back to legacy INTx
924360aebd8SGavin Shan 	 * if MSI doesn't work either
925360aebd8SGavin Shan 	 */
926360aebd8SGavin Shan 	ret = pci_enable_msi(pdev);
927360aebd8SGavin Shan 	if (!ret || pdev->irq) {
928360aebd8SGavin Shan 		irq = pdev->irq;
929360aebd8SGavin Shan 		pnv_php_init_irq(php_slot, irq);
930360aebd8SGavin Shan 	}
931360aebd8SGavin Shan }
932360aebd8SGavin Shan 
pnv_php_register_one(struct device_node * dn)93366725152SGavin Shan static int pnv_php_register_one(struct device_node *dn)
93466725152SGavin Shan {
93566725152SGavin Shan 	struct pnv_php_slot *php_slot;
93639f0d6fbSGavin Shan 	u32 prop32;
93766725152SGavin Shan 	int ret;
93866725152SGavin Shan 
93966725152SGavin Shan 	/* Check if it's hotpluggable slot */
94039f0d6fbSGavin Shan 	ret = of_property_read_u32(dn, "ibm,slot-pluggable", &prop32);
94139f0d6fbSGavin Shan 	if (ret || !prop32)
94266725152SGavin Shan 		return -ENXIO;
94366725152SGavin Shan 
94439f0d6fbSGavin Shan 	ret = of_property_read_u32(dn, "ibm,reset-by-firmware", &prop32);
94539f0d6fbSGavin Shan 	if (ret || !prop32)
94666725152SGavin Shan 		return -ENXIO;
94766725152SGavin Shan 
94866725152SGavin Shan 	php_slot = pnv_php_alloc_slot(dn);
949149ba66aSGavin Shan 	if (!php_slot)
95066725152SGavin Shan 		return -ENODEV;
95166725152SGavin Shan 
95266725152SGavin Shan 	ret = pnv_php_register_slot(php_slot);
953149ba66aSGavin Shan 	if (ret)
95466725152SGavin Shan 		goto free_slot;
95566725152SGavin Shan 
95666725152SGavin Shan 	ret = pnv_php_enable(php_slot, false);
957149ba66aSGavin Shan 	if (ret)
95866725152SGavin Shan 		goto unregister_slot;
95966725152SGavin Shan 
960360aebd8SGavin Shan 	/* Enable interrupt if the slot supports surprise hotplug */
96139f0d6fbSGavin Shan 	ret = of_property_read_u32(dn, "ibm,slot-surprise-pluggable", &prop32);
96239f0d6fbSGavin Shan 	if (!ret && prop32)
963360aebd8SGavin Shan 		pnv_php_enable_irq(php_slot);
964360aebd8SGavin Shan 
96566725152SGavin Shan 	return 0;
96666725152SGavin Shan 
96766725152SGavin Shan unregister_slot:
96866725152SGavin Shan 	pnv_php_unregister_one(php_slot->dn);
96966725152SGavin Shan free_slot:
97066725152SGavin Shan 	pnv_php_put_slot(php_slot);
97166725152SGavin Shan 	return ret;
97266725152SGavin Shan }
97366725152SGavin Shan 
pnv_php_register(struct device_node * dn)97466725152SGavin Shan static void pnv_php_register(struct device_node *dn)
97566725152SGavin Shan {
97666725152SGavin Shan 	struct device_node *child;
97766725152SGavin Shan 
97866725152SGavin Shan 	/*
97966725152SGavin Shan 	 * The parent slots should be registered before their
98066725152SGavin Shan 	 * child slots.
98166725152SGavin Shan 	 */
98266725152SGavin Shan 	for_each_child_of_node(dn, child) {
98366725152SGavin Shan 		pnv_php_register_one(child);
98466725152SGavin Shan 		pnv_php_register(child);
98566725152SGavin Shan 	}
98666725152SGavin Shan }
98766725152SGavin Shan 
pnv_php_unregister_one(struct device_node * dn)98866725152SGavin Shan static void pnv_php_unregister_one(struct device_node *dn)
98966725152SGavin Shan {
99066725152SGavin Shan 	struct pnv_php_slot *php_slot;
99166725152SGavin Shan 
99266725152SGavin Shan 	php_slot = pnv_php_find_slot(dn);
99366725152SGavin Shan 	if (!php_slot)
99466725152SGavin Shan 		return;
99566725152SGavin Shan 
99666725152SGavin Shan 	php_slot->state = PNV_PHP_STATE_OFFLINE;
99766725152SGavin Shan 	pci_hp_deregister(&php_slot->slot);
99851bbf9beSLukas Wunner 	pnv_php_release(php_slot);
99997c6f25dSSimon Guo 	pnv_php_put_slot(php_slot);
100066725152SGavin Shan }
100166725152SGavin Shan 
pnv_php_unregister(struct device_node * dn)100266725152SGavin Shan static void pnv_php_unregister(struct device_node *dn)
100366725152SGavin Shan {
100466725152SGavin Shan 	struct device_node *child;
100566725152SGavin Shan 
100666725152SGavin Shan 	/* The child slots should go before their parent slots */
100766725152SGavin Shan 	for_each_child_of_node(dn, child) {
100866725152SGavin Shan 		pnv_php_unregister(child);
100966725152SGavin Shan 		pnv_php_unregister_one(child);
101066725152SGavin Shan 	}
101166725152SGavin Shan }
101266725152SGavin Shan 
pnv_php_init(void)101366725152SGavin Shan static int __init pnv_php_init(void)
101466725152SGavin Shan {
101566725152SGavin Shan 	struct device_node *dn;
101666725152SGavin Shan 
101766725152SGavin Shan 	pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
101866725152SGavin Shan 	for_each_compatible_node(dn, NULL, "ibm,ioda2-phb")
101966725152SGavin Shan 		pnv_php_register(dn);
102066725152SGavin Shan 
1021a839bd87SOliver O'Halloran 	for_each_compatible_node(dn, NULL, "ibm,ioda3-phb")
1022a839bd87SOliver O'Halloran 		pnv_php_register(dn);
1023a839bd87SOliver O'Halloran 
1024ea53919bSFrederic Barrat 	for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb")
1025ea53919bSFrederic Barrat 		pnv_php_register_one(dn); /* slot directly under the PHB */
102666725152SGavin Shan 	return 0;
102766725152SGavin Shan }
102866725152SGavin Shan 
pnv_php_exit(void)102966725152SGavin Shan static void __exit pnv_php_exit(void)
103066725152SGavin Shan {
103166725152SGavin Shan 	struct device_node *dn;
103266725152SGavin Shan 
103366725152SGavin Shan 	for_each_compatible_node(dn, NULL, "ibm,ioda2-phb")
103466725152SGavin Shan 		pnv_php_unregister(dn);
1035a839bd87SOliver O'Halloran 
1036a839bd87SOliver O'Halloran 	for_each_compatible_node(dn, NULL, "ibm,ioda3-phb")
1037a839bd87SOliver O'Halloran 		pnv_php_unregister(dn);
1038ea53919bSFrederic Barrat 
1039ea53919bSFrederic Barrat 	for_each_compatible_node(dn, NULL, "ibm,ioda2-npu2-opencapi-phb")
1040ea53919bSFrederic Barrat 		pnv_php_unregister_one(dn); /* slot directly under the PHB */
104166725152SGavin Shan }
104266725152SGavin Shan 
104366725152SGavin Shan module_init(pnv_php_init);
104466725152SGavin Shan module_exit(pnv_php_exit);
104566725152SGavin Shan 
104666725152SGavin Shan MODULE_VERSION(DRIVER_VERSION);
104766725152SGavin Shan MODULE_LICENSE("GPL v2");
104866725152SGavin Shan MODULE_AUTHOR(DRIVER_AUTHOR);
104966725152SGavin Shan MODULE_DESCRIPTION(DRIVER_DESC);
1050