pci-host-common.c 4.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2014 ARM Limited
 *
 * Author: Will Deacon <will.deacon@arm.com>
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
#include <linux/platform_device.h>

25
#include "../ecam.h"
26

27 28
static int gen_pci_parse_request_of_pci_ranges(struct device *dev,
		       struct list_head *resources, struct resource **bus_range)
29 30 31 32 33 34
{
	int err, res_valid = 0;
	struct device_node *np = dev->of_node;
	resource_size_t iobase;
	struct resource_entry *win;

35
	err = of_pci_get_host_bridge_resources(np, 0, 0xff, resources, &iobase);
36 37 38
	if (err)
		return err;

39
	resource_list_for_each_entry(win, resources) {
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
		struct resource *parent, *res = win->res;

		switch (resource_type(res)) {
		case IORESOURCE_IO:
			parent = &ioport_resource;
			err = pci_remap_iospace(res, iobase);
			if (err) {
				dev_warn(dev, "error %d: failed to map resource %pR\n",
					 err, res);
				continue;
			}
			break;
		case IORESOURCE_MEM:
			parent = &iomem_resource;
			res_valid |= !(res->flags & IORESOURCE_PREFETCH);
			break;
		case IORESOURCE_BUS:
57
			*bus_range = res;
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
		default:
			continue;
		}

		err = devm_request_resource(dev, parent, res);
		if (err)
			goto out_release_res;
	}

	if (!res_valid) {
		dev_err(dev, "non-prefetchable memory resource required\n");
		err = -EINVAL;
		goto out_release_res;
	}

	return 0;

out_release_res:
	return err;
}

79 80 81 82 83 84 85
static void gen_pci_unmap_cfg(void *ptr)
{
	pci_ecam_free((struct pci_config_window *)ptr);
}

static struct pci_config_window *gen_pci_init(struct device *dev,
		struct list_head *resources, struct pci_ecam_ops *ops)
86 87
{
	int err;
88 89 90
	struct resource cfgres;
	struct resource *bus_range = NULL;
	struct pci_config_window *cfg;
91

92 93 94 95 96 97
	/* Parse our PCI ranges and request their resources */
	err = gen_pci_parse_request_of_pci_ranges(dev, resources, &bus_range);
	if (err)
		goto err_out;

	err = of_address_to_resource(dev->of_node, 0, &cfgres);
98 99
	if (err) {
		dev_err(dev, "missing \"reg\" property\n");
100
		goto err_out;
101 102
	}

103 104 105 106
	cfg = pci_ecam_create(dev, &cfgres, bus_range, ops);
	if (IS_ERR(cfg)) {
		err = PTR_ERR(cfg);
		goto err_out;
107 108
	}

109 110 111 112 113 114 115 116 117 118
	err = devm_add_action(dev, gen_pci_unmap_cfg, cfg);
	if (err) {
		gen_pci_unmap_cfg(cfg);
		goto err_out;
	}
	return cfg;

err_out:
	pci_free_resource_list(resources);
	return ERR_PTR(err);
119 120 121
}

int pci_host_common_probe(struct platform_device *pdev,
122
			  struct pci_ecam_ops *ops)
123 124 125 126 127
{
	const char *type;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct pci_bus *bus, *child;
128 129
	struct pci_config_window *cfg;
	struct list_head resources;
130 131 132 133 134 135 136 137 138 139

	type = of_get_property(np, "device_type", NULL);
	if (!type || strcmp(type, "pci")) {
		dev_err(dev, "invalid \"device_type\" %s\n", type);
		return -EINVAL;
	}

	of_pci_check_probe_only();

	/* Parse and map our Configuration Space windows */
140 141 142 143
	INIT_LIST_HEAD(&resources);
	cfg = gen_pci_init(dev, &resources, ops);
	if (IS_ERR(cfg))
		return PTR_ERR(cfg);
144 145 146 147 148

	/* Do not reassign resources if probe only */
	if (!pci_has_flag(PCI_PROBE_ONLY))
		pci_add_flags(PCI_REASSIGN_ALL_RSRC | PCI_REASSIGN_ALL_BUS);

149 150
	bus = pci_scan_root_bus(dev, cfg->busr.start, &ops->pci_ops, cfg,
				&resources);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
	if (!bus) {
		dev_err(dev, "Scanning rootbus failed");
		return -ENODEV;
	}

	pci_fixup_irqs(pci_common_swizzle, of_irq_parse_and_map_pci);

	if (!pci_has_flag(PCI_PROBE_ONLY)) {
		pci_bus_size_bridges(bus);
		pci_bus_assign_resources(bus);

		list_for_each_entry(child, &bus->children, node)
			pcie_bus_configure_settings(child);
	}

	pci_bus_add_devices(bus);
	return 0;
}

MODULE_DESCRIPTION("Generic PCI host driver common code");
MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>");
MODULE_LICENSE("GPL v2");