You've already forked linux-rockchip
mirror of
https://github.com/armbian/linux-rockchip.git
synced 2026-01-06 11:08:10 -08:00
FROMLIST: PCI/hotplug: Add GPIO PCIe hotplug driver
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@nvidia.com> Signed-off-by: Shawn Lin <shawn.lin@rock-chips.com> Link: https://patchwork.kernel.org/project/linux-pci/patch/20220930192747.21471-3-vidyas@nvidia.com/ Change-Id: Iafa798ee4d98f195f5d33d80120da0c569132548
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
201
drivers/pci/hotplug/gpio_php.c
Normal file
201
drivers/pci/hotplug/gpio_php.c
Normal file
@@ -0,0 +1,201 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* GPIO based PCI Hotplug Driver.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
40
drivers/pci/hotplug/gpiophp.h
Normal file
40
drivers/pci/hotplug/gpiophp.h
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user