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