#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
#include <linux/pci-ecam.h>
#include <linux/platform_device.h>
#include "pci-host-common.h"
void pci_host_common_delete_ports(void *data)
{
struct list_head *ports = data;
struct pci_host_perst *perst, *tmp_perst;
struct pci_host_port *port, *tmp_port;
list_for_each_entry_safe(port, tmp_port, ports, list) {
list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)
list_del(&perst->list);
list_del(&port->list);
}
}
EXPORT_SYMBOL_GPL(pci_host_common_delete_ports);
static int pci_host_common_parse_perst(struct device *dev,
struct pci_host_port *port,
struct device_node *np)
{
struct pci_host_perst *perst;
struct gpio_desc *reset;
int ret;
if (!of_property_present(np, "reset-gpios"))
goto parse_child_node;
reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset",
GPIOD_ASIS, "PERST#");
if (IS_ERR(reset)) {
if (PTR_ERR(reset) == -EBUSY)
dev_err(dev, "Shared PERST# is not supported\n");
return PTR_ERR(reset);
}
perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL);
if (!perst)
return -ENOMEM;
INIT_LIST_HEAD(&perst->list);
perst->desc = reset;
list_add_tail(&perst->list, &port->perst);
parse_child_node:
for_each_available_child_of_node_scoped(np, child) {
ret = pci_host_common_parse_perst(dev, port, child);
if (ret)
return ret;
}
return 0;
}
static int pci_host_common_parse_port(struct device *dev,
struct pci_host_bridge *bridge,
struct device_node *node)
{
struct pci_host_port *port;
int ret;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
INIT_LIST_HEAD(&port->perst);
ret = pci_host_common_parse_perst(dev, port, node);
if (ret)
return ret;
if (list_empty(&port->perst)) {
if (of_property_present(dev->of_node, "reset-gpios") ||
of_property_present(dev->of_node, "reset-gpio"))
return -ENODEV;
}
INIT_LIST_HEAD(&port->list);
list_add_tail(&port->list, &bridge->ports);
return 0;
}
int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge)
{
int ret = -ENODEV;
for_each_available_child_of_node_scoped(dev->of_node, of_port) {
if (!of_node_is_type(of_port, "pci"))
continue;
ret = pci_host_common_parse_port(dev, bridge, of_port);
if (ret)
goto err_cleanup;
}
if (ret)
return ret;
return devm_add_action_or_reset(dev, pci_host_common_delete_ports,
&bridge->ports);
err_cleanup:
pci_host_common_delete_ports(&bridge->ports);
return ret;
}
EXPORT_SYMBOL_GPL(pci_host_common_parse_ports);
#define PCI_HOST_D3COLD_ALLOWED BIT(0)
#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1)
static void gen_pci_unmap_cfg(void *ptr)
{
pci_ecam_free((struct pci_config_window *)ptr);
}
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops)
{
int err;
struct resource cfgres;
struct resource_entry *bus;
struct pci_config_window *cfg;
err = of_address_to_resource(dev->of_node, 0, &cfgres);
if (err) {
dev_err(dev, "missing or malformed \"reg\" property\n");
return ERR_PTR(err);
}
bus = resource_list_first_type(&bridge->windows, IORESOURCE_BUS);
if (!bus)
return ERR_PTR(-ENODEV);
cfg = pci_ecam_create(dev, &cfgres, bus->res, ops);
if (IS_ERR(cfg))
return cfg;
err = devm_add_action_or_reset(dev, gen_pci_unmap_cfg, cfg);
if (err)
return ERR_PTR(err);
return cfg;
}
EXPORT_SYMBOL_GPL(pci_host_common_ecam_create);
int pci_host_common_init(struct platform_device *pdev,
struct pci_host_bridge *bridge,
const struct pci_ecam_ops *ops)
{
struct device *dev = &pdev->dev;
struct pci_config_window *cfg;
of_pci_check_probe_only();
platform_set_drvdata(pdev, bridge);
cfg = pci_host_common_ecam_create(dev, bridge, ops);
if (IS_ERR(cfg))
return PTR_ERR(cfg);
if (!pci_has_flag(PCI_PROBE_ONLY))
pci_add_flags(PCI_REASSIGN_ALL_BUS);
bridge->sysdata = cfg;
bridge->ops = (struct pci_ops *)&ops->pci_ops;
bridge->enable_device = ops->enable_device;
bridge->disable_device = ops->disable_device;
bridge->msi_domain = true;
return pci_host_probe(bridge);
}
EXPORT_SYMBOL_GPL(pci_host_common_init);
int pci_host_common_probe(struct platform_device *pdev)
{
const struct pci_ecam_ops *ops;
struct pci_host_bridge *bridge;
ops = of_device_get_match_data(&pdev->dev);
if (!ops)
return -ENODEV;
bridge = devm_pci_alloc_host_bridge(&pdev->dev, 0);
if (!bridge)
return -ENOMEM;
return pci_host_common_init(pdev, bridge, ops);
}
EXPORT_SYMBOL_GPL(pci_host_common_probe);
void pci_host_common_remove(struct platform_device *pdev)
{
struct pci_host_bridge *bridge = platform_get_drvdata(pdev);
pci_lock_rescan_remove();
pci_stop_root_bus(bridge->bus);
pci_remove_root_bus(bridge->bus);
pci_unlock_rescan_remove();
}
EXPORT_SYMBOL_GPL(pci_host_common_remove);
static int __pci_host_common_d3cold_possible(struct pci_dev *pdev,
void *userdata)
{
u32 *flags = userdata;
if (!pdev->dev.driver && !pci_is_enabled(pdev))
return 0;
if (pdev->current_state != PCI_D3hot)
goto exit;
if (device_may_wakeup(&pdev->dev)) {
if (!pci_pme_capable(pdev, PCI_D3cold))
goto exit;
else
*flags |= PCI_HOST_PME_D3COLD_CAPABLE;
}
return 0;
exit:
*flags &= ~PCI_HOST_D3COLD_ALLOWED;
return -EOPNOTSUPP;
}
bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge,
bool *pme_capable)
{
u32 flags = PCI_HOST_D3COLD_ALLOWED;
pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags);
*pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE);
return !!(flags & PCI_HOST_D3COLD_ALLOWED);
}
EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible);
MODULE_DESCRIPTION("Common library for PCI host controller drivers");
MODULE_LICENSE("GPL v2");