Skip to content
pci-ioda.c 37.8 KiB
Newer Older
/*
 * Support PCI/PCIe on PowerNV platforms
 *
 * Copyright 2011 Benjamin Herrenschmidt, IBM Corp.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */


#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/bootmem.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/msi.h>

#include <asm/sections.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/pci-bridge.h>
#include <asm/machdep.h>
#include <asm/ppc-pci.h>
#include <asm/opal.h>
#include <asm/iommu.h>
#include <asm/tce.h>

#include "powernv.h"
#include "pci.h"

struct resource_wrap {
	struct list_head	link;
	resource_size_t		size;
	resource_size_t		align;
	struct pci_dev		*dev;	/* Set if it's a device */
	struct pci_bus		*bus;	/* Set if it's a bridge */
};

static int __pe_printk(const char *level, const struct pnv_ioda_pe *pe,
		       struct va_format *vaf)
{
	char pfix[32];

	if (pe->pdev)
		strlcpy(pfix, dev_name(&pe->pdev->dev), sizeof(pfix));
	else
		sprintf(pfix, "%04x:%02x     ",
			pci_domain_nr(pe->pbus), pe->pbus->number);
	return printk("pci %s%s: [PE# %.3d] %pV", level, pfix, pe->pe_number, vaf);
}

#define define_pe_printk_level(func, kern_level)		\
static int func(const struct pnv_ioda_pe *pe, const char *fmt, ...)	\
{								\
	struct va_format vaf;					\
	va_list args;						\
	int r;							\
								\
	va_start(args, fmt);					\
								\
	vaf.fmt = fmt;						\
	vaf.va = &args;						\
								\
	r = __pe_printk(kern_level, pe, &vaf);			\
	va_end(args);						\
								\
	return r;						\
}								\

define_pe_printk_level(pe_err, KERN_ERR);
define_pe_printk_level(pe_warn, KERN_WARNING);
define_pe_printk_level(pe_info, KERN_INFO);


/* Calculate resource usage & alignment requirement of a single
 * device. This will also assign all resources within the device
 * for a given type starting at 0 for the biggest one and then
 * assigning in decreasing order of size.
 */
static void __devinit pnv_ioda_calc_dev(struct pci_dev *dev, unsigned int flags,
					resource_size_t *size,
					resource_size_t *align)
{
	resource_size_t start;
	struct resource *r;
	int i;

	pr_devel("  -> CDR %s\n", pci_name(dev));

	*size = *align = 0;

	/* Clear the resources out and mark them all unset */
	for (i = 0; i <= PCI_ROM_RESOURCE; i++) {
		r = &dev->resource[i];
		if (!(r->flags & flags))
		    continue;
		if (r->start) {
			r->end -= r->start;
			r->start = 0;
		}
		r->flags |= IORESOURCE_UNSET;
	}

	/* We currently keep all memory resources together, we
	 * will handle prefetch & 64-bit separately in the future
	 * but for now we stick everybody in M32
	 */
	start = 0;
	for (;;) {
		resource_size_t max_size = 0;
		int max_no = -1;

		/* Find next biggest resource */
		for (i = 0; i <= PCI_ROM_RESOURCE; i++) {
			r = &dev->resource[i];
			if (!(r->flags & IORESOURCE_UNSET) ||
			    !(r->flags & flags))
				continue;
			if (resource_size(r) > max_size) {
				max_size = resource_size(r);
				max_no = i;
			}
		}
		if (max_no < 0)
			break;
		r = &dev->resource[max_no];
		if (max_size > *align)
			*align = max_size;
		*size += max_size;
		r->start = start;
		start += max_size;
		r->end = r->start + max_size - 1;
		r->flags &= ~IORESOURCE_UNSET;
		pr_devel("  ->     R%d %016llx..%016llx\n",
			 max_no, r->start, r->end);
	}
	pr_devel("  <- CDR %s size=%llx align=%llx\n",
		 pci_name(dev), *size, *align);
}

/* Allocate a resource "wrap" for a given device or bridge and
 * insert it at the right position in the sorted list
 */
static void __devinit pnv_ioda_add_wrap(struct list_head *list,
					struct pci_bus *bus,
					struct pci_dev *dev,
					resource_size_t size,
					resource_size_t align)
{
	struct resource_wrap *w1, *w = kzalloc(sizeof(*w), GFP_KERNEL);

	w->size = size;
	w->align = align;
	w->dev = dev;
	w->bus = bus;

	list_for_each_entry(w1, list, link) {
		if (w1->align < align) {
			list_add_tail(&w->link, &w1->link);
			return;
		}
	}
	list_add_tail(&w->link, list);
}

/* Offset device resources of a given type */
static void __devinit pnv_ioda_offset_dev(struct pci_dev *dev,
					  unsigned int flags,
					  resource_size_t offset)
{
	struct resource *r;
	int i;

	pr_devel("  -> ODR %s [%x] +%016llx\n", pci_name(dev), flags, offset);

	for (i = 0; i <= PCI_ROM_RESOURCE; i++) {
		r = &dev->resource[i];
		if (r->flags & flags) {
			dev->resource[i].start += offset;
			dev->resource[i].end += offset;
		}
	}

	pr_devel("  <- ODR %s [%x] +%016llx\n", pci_name(dev), flags, offset);
}

/* Offset bus resources (& all children) of a given type */
static void __devinit pnv_ioda_offset_bus(struct pci_bus *bus,
					  unsigned int flags,
					  resource_size_t offset)
{
	struct resource *r;
	struct pci_dev *dev;
	struct pci_bus *cbus;
Loading
Loading full blame...