/*
 * ACPI PCI HotPlug PCI configuration space management
 *
 * Copyright (c) 1995,2001 Compaq Computer Corporation
 * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com)
 * Copyright (c) 2001,2002 IBM Corp.
 * Copyright (c) 2002 Takayoshi Kochi (t-kouchi@cq.jp.nec.com)
 * Copyright (c) 2002 Hiroshi Aono (h-aono@ap.jp.nec.com)
 * Copyright (c) 2002 NEC Corporation
 *
 * All rights reserved.
 *
 * 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.
 *
 * 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, GOOD TITLE or
 * NON INFRINGEMENT.  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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Send feedback to <t-kouchi@cq.jp.nec.com>
 *
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include "pci_hotplug.h"
#include "acpiphp.h"

#define MY_NAME "acpiphp_pci"


/* allocate mem/pmem/io resource to a new function */
static int init_config_space (struct acpiphp_func *func)
{
	u32 bar, len;
	u32 address[] = {
		PCI_BASE_ADDRESS_0,
		PCI_BASE_ADDRESS_1,
		PCI_BASE_ADDRESS_2,
		PCI_BASE_ADDRESS_3,
		PCI_BASE_ADDRESS_4,
		PCI_BASE_ADDRESS_5,
		0
	};
	int count;
	struct acpiphp_bridge *bridge;
	struct pci_resource *res;
	struct pci_bus *pbus;
	int bus, device, function;
	unsigned int devfn;
	u16 tmp;

	bridge = func->slot->bridge;
	pbus = bridge->pci_bus;
	bus = bridge->bus;
	device = func->slot->device;
	function = func->function;
	devfn = PCI_DEVFN(device, function);

	for (count = 0; address[count]; count++) {	/* for 6 BARs */
		pci_bus_write_config_dword(pbus, devfn,
					   address[count], 0xFFFFFFFF);
		pci_bus_read_config_dword(pbus, devfn, address[count], &bar);

		if (!bar)	/* This BAR is not implemented */
			continue;

		dbg("Device %02x.%02x BAR %d wants %x\n", device, function, count, bar);

		if (bar & PCI_BASE_ADDRESS_SPACE_IO) {
			/* This is IO */

			len = bar & 0xFFFFFFFC;
			len = ~len + 1;

			dbg("len in IO %x, BAR %d\n", len, count);

			spin_lock(&bridge->res_lock);
			res = acpiphp_get_io_resource(&bridge->io_head, len);
			spin_unlock(&bridge->res_lock);

			if (!res) {
				err("cannot allocate requested io for %02x:%02x.%d len %x\n",
				    bus, device, function, len);
				return -1;
			}
			pci_bus_write_config_dword(pbus, devfn,
						   address[count],
						   (u32)res->base);
			res->next = func->io_head;
			func->io_head = res;

		} else {
			/* This is Memory */
			if (bar & PCI_BASE_ADDRESS_MEM_PREFETCH) {
				/* pfmem */

				len = bar & 0xFFFFFFF0;
				len = ~len + 1;

				dbg("len in PFMEM %x, BAR %d\n", len, count);

				spin_lock(&bridge->res_lock);
				res = acpiphp_get_resource(&bridge->p_mem_head, len);
				spin_unlock(&bridge->res_lock);

				if (!res) {
					err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n",
					    bus, device, function, len);
					return -1;
				}

				pci_bus_write_config_dword(pbus, devfn,
							   address[count],
							   (u32)res->base);

				if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) {	/* takes up another dword */
					dbg("inside the pfmem 64 case, count %d\n", count);
					count += 1;
					pci_bus_write_config_dword(pbus, devfn,
								   address[count],
								   (u32)(res->base >> 32));
				}

				res->next = func->p_mem_head;
				func->p_mem_head = res;

			} else {
				/* regular memory */

				len = bar & 0xFFFFFFF0;
				len = ~len + 1;

				dbg("len in MEM %x, BAR %d\n", len, count);

				spin_lock(&bridge->res_lock);
				res = acpiphp_get_resource(&bridge->mem_head, len);
				spin_unlock(&bridge->res_lock);

				if (!res) {
					err("cannot allocate requested pfmem for %02x:%02x.%d len %x\n",
					    bus, device, function, len);
					return -1;
				}

				pci_bus_write_config_dword(pbus, devfn,
							   address[count],
							   (u32)res->base);

				if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) {
					/* takes up another dword */
					dbg("inside mem 64 case, reg. mem, count %d\n", count);
					count += 1;
					pci_bus_write_config_dword(pbus, devfn,
								   address[count],
								   (u32)(res->base >> 32));
				}

				res->next = func->mem_head;
				func->mem_head = res;

			}
		}
	}

	/* disable expansion rom */
	pci_bus_write_config_dword(pbus, devfn, PCI_ROM_ADDRESS, 0x00000000);

	/* set PCI parameters from _HPP */
	pci_bus_write_config_byte(pbus, devfn, PCI_CACHE_LINE_SIZE,
				  bridge->hpp.cache_line_size);
	pci_bus_write_config_byte(pbus, devfn, PCI_LATENCY_TIMER,
				  bridge->hpp.latency_timer);

	pci_bus_read_config_word(pbus, devfn, PCI_COMMAND, &tmp);
	if (bridge->hpp.enable_SERR)
		tmp |= PCI_COMMAND_SERR;
	if (bridge->hpp.enable_PERR)
		tmp |= PCI_COMMAND_PARITY;
	pci_bus_write_config_word(pbus, devfn, PCI_COMMAND, tmp);

	return 0;
}

/* detect_used_resource - subtract resource under dev from bridge */
static int detect_used_resource (struct acpiphp_bridge *bridge, struct pci_dev *dev)
{
	u32 bar, len;
	u64 base;
	u32 address[] = {
		PCI_BASE_ADDRESS_0,
		PCI_BASE_ADDRESS_1,
		PCI_BASE_ADDRESS_2,
		PCI_BASE_ADDRESS_3,
		PCI_BASE_ADDRESS_4,
		PCI_BASE_ADDRESS_5,
		0
	};
	int count;
	struct pci_resource *res;

	dbg("Device %s\n", dev->slot_name);

	for (count = 0; address[count]; count++) {	/* for 6 BARs */
		pci_read_config_dword(dev, address[count], &bar);

		if (!bar)	/* This BAR is not implemented */
			continue;

		pci_write_config_dword(dev, address[count], 0xFFFFFFFF);
		pci_read_config_dword(dev, address[count], &len);

		if (len & PCI_BASE_ADDRESS_SPACE_IO) {
			/* This is IO */
			base = bar & 0xFFFFFFFC;
			len &= 0xFFFFFFFC;
			len = ~len + 1;

			dbg("BAR[%d] %08x - %08x (IO)\n", count, (u32)base, (u32)base + len - 1);

			spin_lock(&bridge->res_lock);
			res = acpiphp_get_resource_with_base(&bridge->io_head, base, len);
			spin_unlock(&bridge->res_lock);
			if (res)
				kfree(res);
		} else {
			/* This is Memory */
			base = bar & 0xFFFFFFF0;
			if (len & PCI_BASE_ADDRESS_MEM_PREFETCH) {
				/* pfmem */

				len &= 0xFFFFFFF0;
				len = ~len + 1;

				if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) {	/* takes up another dword */
					dbg("prefetch mem 64\n");
					count += 1;
				}
				dbg("BAR[%d] %08x - %08x (PMEM)\n", count, (u32)base, (u32)base + len - 1);
				spin_lock(&bridge->res_lock);
				res = acpiphp_get_resource_with_base(&bridge->p_mem_head, base, len);
				spin_unlock(&bridge->res_lock);
				if (res)
					kfree(res);
			} else {
				/* regular memory */

				len &= 0xFFFFFFF0;
				len = ~len + 1;

				if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) {
					/* takes up another dword */
					dbg("mem 64\n");
					count += 1;
				}
				dbg("BAR[%d] %08x - %08x (MEM)\n", count, (u32)base, (u32)base + len - 1);
				spin_lock(&bridge->res_lock);
				res = acpiphp_get_resource_with_base(&bridge->mem_head, base, len);
				spin_unlock(&bridge->res_lock);
				if (res)
					kfree(res);
			}
		}

		pci_write_config_dword(dev, address[count], bar);
	}

	return 0;
}


/* detect_pci_resource_bus - subtract resource under pci_bus */
static void detect_used_resource_bus(struct acpiphp_bridge *bridge, struct pci_bus *bus)
{
	struct list_head *l;
	struct pci_dev *dev;

	list_for_each (l, &bus->devices) {
		dev = pci_dev_b(l);
		detect_used_resource(bridge, dev);
		/* XXX recursive call */
		if (dev->subordinate)
			detect_used_resource_bus(bridge, dev->subordinate);
	}
}


/**
 * acpiphp_detect_pci_resource - detect resources under bridge
 * @bridge: detect all resources already used under this bridge
 *
 * collect all resources already allocated for all devices under a bridge.
 */
int acpiphp_detect_pci_resource (struct acpiphp_bridge *bridge)
{
	detect_used_resource_bus(bridge, bridge->pci_bus);

	return 0;
}


/**
 * acpiphp_init_slot_resource - gather resource usage information of a slot
 * @slot: ACPI slot object to be checked, should have valid pci_dev member
 *
 * TBD: PCI-to-PCI bridge case
 *      use pci_dev->resource[]
 */
int acpiphp_init_func_resource (struct acpiphp_func *func)
{
	u64 base;
	u32 bar, len;
	u32 address[] = {
		PCI_BASE_ADDRESS_0,
		PCI_BASE_ADDRESS_1,
		PCI_BASE_ADDRESS_2,
		PCI_BASE_ADDRESS_3,
		PCI_BASE_ADDRESS_4,
		PCI_BASE_ADDRESS_5,
		0
	};
	int count;
	struct pci_resource *res;
	struct pci_dev *dev;

	dev = func->pci_dev;
	dbg("Hot-pluggable device %s\n", dev->slot_name);

	for (count = 0; address[count]; count++) {	/* for 6 BARs */
		pci_read_config_dword(dev, address[count], &bar);

		if (!bar)	/* This BAR is not implemented */
			continue;

		pci_write_config_dword(dev, address[count], 0xFFFFFFFF);
		pci_read_config_dword(dev, address[count], &len);

		if (len & PCI_BASE_ADDRESS_SPACE_IO) {
			/* This is IO */
			base = bar & 0xFFFFFFFC;
			len &= 0xFFFFFFFC;
			len = ~len + 1;

			dbg("BAR[%d] %08x - %08x (IO)\n", count, (u32)base, (u32)base + len - 1);

			res = acpiphp_make_resource(base, len);
			if (!res)
				goto no_memory;

			res->next = func->io_head;
			func->io_head = res;

		} else {
			/* This is Memory */
			base = bar & 0xFFFFFFF0;
			if (len & PCI_BASE_ADDRESS_MEM_PREFETCH) {
				/* pfmem */

				len &= 0xFFFFFFF0;
				len = ~len + 1;

				if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) {	/* takes up another dword */
					dbg("prefetch mem 64\n");
					count += 1;
				}
				dbg("BAR[%d] %08x - %08x (PMEM)\n", count, (u32)base, (u32)base + len - 1);
				res = acpiphp_make_resource(base, len);
				if (!res)
					goto no_memory;

				res->next = func->p_mem_head;
				func->p_mem_head = res;

			} else {
				/* regular memory */

				len &= 0xFFFFFFF0;
				len = ~len + 1;

				if (len & PCI_BASE_ADDRESS_MEM_TYPE_64) {
					/* takes up another dword */
					dbg("mem 64\n");
					count += 1;
				}
				dbg("BAR[%d] %08x - %08x (MEM)\n", count, (u32)base, (u32)base + len - 1);
				res = acpiphp_make_resource(base, len);
				if (!res)
					goto no_memory;

				res->next = func->mem_head;
				func->mem_head = res;

			}
		}

		pci_write_config_dword(dev, address[count], bar);
	}
#if 1
	acpiphp_dump_func_resource(func);
#endif

	return 0;

 no_memory:
	err("out of memory\n");
	acpiphp_free_resource(&func->io_head);
	acpiphp_free_resource(&func->mem_head);
	acpiphp_free_resource(&func->p_mem_head);

	return -1;
}


/**
 * acpiphp_configure_slot - allocate PCI resources
 * @slot: slot to be configured
 *
 * initializes a PCI functions on a device inserted
 * into the slot
 *
 */
int acpiphp_configure_slot (struct acpiphp_slot *slot)
{
	struct acpiphp_func *func;
	struct list_head *l;
	u8 hdr;
	u32 dvid;
	int retval = 0;
	int is_multi = 0;

	pci_bus_read_config_byte(slot->bridge->pci_bus,
				 PCI_DEVFN(slot->device, 0),
				 PCI_HEADER_TYPE, &hdr);

	if (hdr & 0x80)
		is_multi = 1;

	list_for_each (l, &slot->funcs) {
		func = list_entry(l, struct acpiphp_func, sibling);
		if (is_multi || func->function == 0) {
			pci_bus_read_config_dword(slot->bridge->pci_bus,
						  PCI_DEVFN(slot->device,
							    func->function),
						  PCI_VENDOR_ID, &dvid);
			if (dvid != 0xffffffff) {
				retval = init_config_space(func);
				if (retval)
					break;
			}
		}
	}

	return retval;
}

/**
 * acpiphp_configure_function - configure PCI function
 * @func: function to be configured
 *
 * initializes a PCI functions on a device inserted
 * into the slot
 *
 */
int acpiphp_configure_function (struct acpiphp_func *func)
{
	/* all handled by the pci core now */
	return 0;
}

/**
 * acpiphp_unconfigure_function - unconfigure PCI function
 * @func: function to be unconfigured
 *
 */
int acpiphp_unconfigure_function (struct acpiphp_func *func)
{
	struct acpiphp_bridge *bridge;
	int retval = 0;

	/* if pci_dev is NULL, ignore it */
	if (!func->pci_dev)
		goto err_exit;

	pci_remove_bus_device(func->pci_dev);

	/* free all resources */
	bridge = func->slot->bridge;

	spin_lock(&bridge->res_lock);
	acpiphp_move_resource(&func->io_head, &bridge->io_head);
	acpiphp_move_resource(&func->mem_head, &bridge->mem_head);
	acpiphp_move_resource(&func->p_mem_head, &bridge->p_mem_head);
	acpiphp_move_resource(&func->bus_head, &bridge->bus_head);
	spin_unlock(&bridge->res_lock);

 err_exit:
	return retval;
}
