This adds a standalone driver to support PCIe hotplug functionality merely based on a GPIO indicating the status of a downstream device connectivity. It looks for "hotplug-gpios" property in the corresponding device node to get the GPIO information. It also provides a mechanism for platform drivers of the controllers to register ops to perform any platform specific operations while enabling/disabling the slots. Signed-off-by: Vidya Sagar <vidyas@xxxxxxxxxx> --- drivers/pci/hotplug/Kconfig | 11 ++ drivers/pci/hotplug/Makefile | 1 + drivers/pci/hotplug/gpio_php.c | 200 +++++++++++++++++++++++++++++++++ drivers/pci/hotplug/gpiophp.h | 40 +++++++ 4 files changed, 252 insertions(+) create mode 100644 drivers/pci/hotplug/gpio_php.c create mode 100644 drivers/pci/hotplug/gpiophp.h diff --git a/drivers/pci/hotplug/Kconfig b/drivers/pci/hotplug/Kconfig index 840a84bb5ee2..dbac64ff6915 100644 --- a/drivers/pci/hotplug/Kconfig +++ b/drivers/pci/hotplug/Kconfig @@ -158,4 +158,15 @@ config HOTPLUG_PCI_S390 When in doubt, say Y. +config HOTPLUG_PCI_GPIO + bool "GPIO based PCI Hotplug Support" + depends on OF_GPIO + help + Say Y here if you want to have GPIO based PCIe hot-plug framework. + This framework helps to register a GPIO based Hot-Plug controller + with the system where a GPIO can be used to represent device + connect and disconnect states. + + When in doubt, say N. + endif # HOTPLUG_PCI diff --git a/drivers/pci/hotplug/Makefile b/drivers/pci/hotplug/Makefile index 5196983220df..70e11d698807 100644 --- a/drivers/pci/hotplug/Makefile +++ b/drivers/pci/hotplug/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_HOTPLUG_PCI_RPA) += rpaphp.o obj-$(CONFIG_HOTPLUG_PCI_RPA_DLPAR) += rpadlpar_io.o obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o obj-$(CONFIG_HOTPLUG_PCI_S390) += s390_pci_hpc.o +obj-$(CONFIG_HOTPLUG_PCI_GPIO) += gpio_php.o # acpiphp_ibm extends acpiphp, so should be linked afterwards. diff --git a/drivers/pci/hotplug/gpio_php.c b/drivers/pci/hotplug/gpio_php.c new file mode 100644 index 000000000000..33c8105aade5 --- /dev/null +++ b/drivers/pci/hotplug/gpio_php.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPIO based PCI Hotplug Driver. + * + */ + +#include <linux/libfdt.h> +#include <linux/module.h> +#include <linux/of_fdt.h> +#include <linux/pci.h> +#include <linux/pm_runtime.h> +#include "../pci.h" +#include "../pcie/portdrv.h" + +#include "gpiophp.h" + +static DEFINE_MUTEX(slot_mutex); + +static inline struct gpio_hotplug_slot *to_gpio_hotplug_slot(struct hotplug_slot *slot) +{ + return container_of(slot, struct gpio_hotplug_slot, hotplug_slot); +} + +static int gpio_hp_get_power_status(struct hotplug_slot *hotplug_slot, u8 *value) +{ + struct gpio_hotplug_slot *slot = to_gpio_hotplug_slot(hotplug_slot); + struct pci_dev *dev; + + pci_config_pm_runtime_get(slot->pdev); + dev = pci_get_slot(slot->pdev->subordinate, PCI_DEVFN(0, 0)); + if (dev) { + pci_dev_put(dev); + *value = 1; + } else { + *value = 0; + } + pci_dbg(slot->pdev, "Power Status = %u\n", *value); + pci_config_pm_runtime_put(slot->pdev); + + return 0; +} + +static int gpio_hp_enable_slot(struct hotplug_slot *hotplug_slot) +{ + struct gpio_hotplug_slot *slot = to_gpio_hotplug_slot(hotplug_slot); + int ret = 0; + u8 value; + + mutex_lock(&slot_mutex); + + gpio_hp_get_power_status(hotplug_slot, &value); + if (value) { + pci_info(slot->pdev, "Device is already plugged-in\n"); + goto exit; + } + + if (slot->plat_ops && slot->plat_ops->enable) + slot->plat_ops->enable(slot); + + pm_runtime_get_sync(&slot->pdev->dev); + + pci_lock_rescan_remove(); + pci_rescan_bus(slot->pdev->bus); + pci_unlock_rescan_remove(); + + pm_runtime_put(&slot->pdev->dev); + +exit: + mutex_unlock(&slot_mutex); + return ret; +} + +static int gpio_hp_disable_slot(struct hotplug_slot *hotplug_slot) +{ + struct gpio_hotplug_slot *slot = to_gpio_hotplug_slot(hotplug_slot); + struct pci_dev *dev, *temp; + u8 value; + + mutex_lock(&slot_mutex); + + gpio_hp_get_power_status(hotplug_slot, &value); + if (!value) { + pci_info(slot->pdev, "Device is already removed\n"); + goto exit; + } + + pci_lock_rescan_remove(); + + list_for_each_entry_safe_reverse(dev, temp, &slot->pdev->subordinate->devices, bus_list) { + pci_dev_get(dev); + pci_stop_and_remove_bus_device(dev); + pci_dev_put(dev); + } + + pci_unlock_rescan_remove(); + +exit: + if (slot->plat_ops && slot->plat_ops->disable) + slot->plat_ops->disable(slot); + mutex_unlock(&slot_mutex); + return 0; +} + +static const struct hotplug_slot_ops gpio_hotplug_slot_ops = { + .enable_slot = gpio_hp_enable_slot, + .disable_slot = gpio_hp_disable_slot, + .get_power_status = gpio_hp_get_power_status, +}; + +static irqreturn_t pcie_gpio_hp_irq(int irq, void *arg) +{ + struct gpio_hotplug_slot *slot = arg; + + if (gpiod_get_value(slot->gpiod)) { + pci_dbg(slot->pdev, "Hot-Plug Event\n"); + gpio_hp_enable_slot(&slot->hotplug_slot); + } else { + pci_dbg(slot->pdev, "Hot-UnPlug Event\n"); + gpio_hp_disable_slot(&slot->hotplug_slot); + } + + return IRQ_HANDLED; +} + +int register_gpio_hotplug_slot(struct gpio_hotplug_slot *slot) +{ + struct device *dev = &slot->pdev->dev; + struct pci_dev *pdev = slot->pdev; + struct gpio_desc *gpiod; + unsigned int irq; + char *name; + int ret; + + gpiod = devm_gpiod_get(&pdev->bus->dev, "hotplug", GPIOD_IN); + if (IS_ERR(gpiod)) { + ret = PTR_ERR(gpiod); + pci_err(pdev, "Failed to find GPIO for Hot-Plug functionality: %d\n", ret); + return ret; + } + + ret = gpiod_to_irq(gpiod); + if (ret < 0) { + pci_err(pdev, "Failed to get IRQ for Hot_Plug GPIO: %d\n", ret); + return ret; + } + irq = (unsigned int)ret; + + slot->gpiod = gpiod; + slot->hotplug_slot.ops = &gpio_hotplug_slot_ops; + slot->irq = irq; + + name = devm_kasprintf(dev, GFP_KERNEL, "slot_%u", slot->slot_nr); + if (!name) { + ret = -ENOMEM; + goto exit; + } + + ret = pci_hp_register(&slot->hotplug_slot, pdev->subordinate, 0, name); + if (ret) { + pci_err(pdev, "Failed to register hotplug slot: %d\n", ret); + goto exit; + } + + name = devm_kasprintf(dev, GFP_KERNEL, "pcie_gpio_hp_irq_%u", slot->slot_nr); + if (!name) { + ret = -ENOMEM; + goto exit; + } + + ret = devm_request_threaded_irq(dev, slot->irq, + NULL, pcie_gpio_hp_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + name, slot); + if (ret < 0) { + pci_err(pdev, "Failed to request IRQ for Hot-Plug: %d\n", ret); + goto exit; + } + + pci_dbg(pdev, "Hot-Plug Slot registered for %s\n", pci_name(pdev)); + + gpio_hp_enable_slot(&slot->hotplug_slot); + + return 0; + +exit: + return ret; +} + +void unregister_gpio_hotplug_slot(struct gpio_hotplug_slot *slot) +{ + struct device *dev = &slot->pdev->dev; + struct pci_dev *pdev = slot->pdev; + + gpio_hp_disable_slot(&slot->hotplug_slot); + devm_free_irq(dev, slot->irq, slot); + pci_hp_deregister(&slot->hotplug_slot); + devm_gpiod_put(&pdev->bus->dev, slot->gpiod); +} + diff --git a/drivers/pci/hotplug/gpiophp.h b/drivers/pci/hotplug/gpiophp.h new file mode 100644 index 000000000000..77cc76976e0d --- /dev/null +++ b/drivers/pci/hotplug/gpiophp.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * GPIO based PCI Hotplug Driver. + * + */ + +#ifndef _GPIOPHP_H +#define _GPIOPHP_H + +#include <linux/pci_hotplug.h> + +struct gpio_hotplug_slot; + +struct gpio_hotplug_slot_plat_ops { + int (*enable)(struct gpio_hotplug_slot *hp_slot); + int (*disable)(struct gpio_hotplug_slot *hp_slot); +}; + +struct gpio_hotplug_slot { + struct device_node *np; + int slot_nr; + const struct gpio_hotplug_slot_plat_ops *plat_ops; + struct pci_dev *pdev; + + struct gpio_desc *gpiod; + unsigned int irq; + + struct hotplug_slot hotplug_slot; +}; + +#ifdef CONFIG_HOTPLUG_PCI_GPIO +int register_gpio_hotplug_slot(struct gpio_hotplug_slot *hp_slot); +void unregister_gpio_hotplug_slot(struct gpio_hotplug_slot *hp_slot); +#else +static inline int register_gpio_hotplug_slot(struct gpio_hotplug_slot *hp_slot) +{ return 0; } +static inline void unregister_gpio_hotplug_slot(struct gpio_hotplug_slot *hp_slot) {} +#endif + +#endif //_GPIOPHP_H -- 2.17.1