/*
* pci_link.c - ACPI PCI Interrupt Link Device Driver ($Revision: 34 $)
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
* Copyright (C) 2002 Dominik Brodowski <devel@brodo.de>
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 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. 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* TBD:
* 1. Support more than one IRQ resource entry per link device (index).
* 2. Implement start/stop mechanism and use ACPI Bus Driver facilities
* for IRQ management (e.g. start()->_SRS).
*/
#include <linux/syscore_ops.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/pm.h>
#include <linux/pci.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include "internal.h"
#define _COMPONENT ACPI_PCI_COMPONENT
ACPI_MODULE_NAME("pci_link");
#define ACPI_PCI_LINK_CLASS "pci_irq_routing"
#define ACPI_PCI_LINK_DEVICE_NAME "PCI Interrupt Link"
#define ACPI_PCI_LINK_FILE_INFO "info"
#define ACPI_PCI_LINK_FILE_STATUS "state"
#define ACPI_PCI_LINK_MAX_POSSIBLE 16
static int acpi_pci_link_add(struct acpi_device *device,
const struct acpi_device_id *not_used);
static void acpi_pci_link_remove(struct acpi_device *device);
static const struct acpi_device_id link_device_ids[] = {
{"PNP0C0F", 0},
{"", 0},
};
static struct acpi_scan_handler pci_link_handler = {
.ids = link_device_ids,
.attach = acpi_pci_link_add,
.detach = acpi_pci_link_remove,
};
/*
* If a link is initialized, we never change its active and initialized
* later even the link is disable. Instead, we just repick the active irq
*/
struct acpi_pci_link_irq {
u8 active; /* Current IRQ */
u8 triggering; /* All IRQs */
u8 polarity; /* All IRQs */
u8 resource_type;
u8 possible_count;
u8 possible[ACPI_PCI_LINK_MAX_POSSIBLE];
u8 initialized:1;
u8 reserved:7;
};
struct acpi_pci_link {
struct list_head list;
struct acpi_device *device;
struct acpi_pci_link_irq irq;
int refcnt;
};
static LIST_HEAD(acpi_link_list);
static DEFINE_MUTEX(acpi_link_lock);
/* --------------------------------------------------------------------------
PCI Link Device Management
-------------------------------------------------------------------------- */
/*
* set context (link) possible list from resource list
*/
static acpi_status acpi_pci_link_check_possible(struct acpi_resource *resource,
void *context)
{
struct acpi_pci_link *link = context;
u32 i;
switch (resource->type) {
case ACPI_RESOURCE_TYPE_START_DEPENDENT:
case ACPI_RESOURCE_TYPE_END_TAG:
return AE_OK;
case ACPI_RESOURCE_TYPE_IRQ:
{
struct acpi_resource_irq *p = &resource->data.irq;
if (!p || !p->interrupt_count) {
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Blank _PRS IRQ resource\n"));
return AE_OK;
}
for (i = 0;
(i < p->interrupt_count
&& i < ACPI_PCI_LINK_MAX_POSSIBLE); i++) {
if (!p->interrupts[i]) {
printk(KERN_WARNING PREFIX
"Invalid _PRS IRQ %d\n",
p->interrupts[i]);
continue;
}
link->irq.possible[i] = p->interrupts[i];
link->irq.possible_count++;
}
link->irq.triggering = p->triggering;
link->irq.polarity = p->polarity;
link->irq.resource_type = ACPI_RESOURCE_TYPE_IRQ;
break;
}
case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
{
struct acpi_resource_extended_irq *p =
&resource->data.extended_irq;
if (!p || !p->interrupt_count) {
printk(KERN_WARNING PREFIX
"Blank _PRS EXT IRQ resource\n");
return AE_OK;
}
for (i = 0;
(i < p->interrupt_count
&& i < ACPI_PCI_LINK_MAX_POSSIBLE); i++) {
if (!p->interrupts[i]) {
printk(KERN_WARNING PREFIX
"Invalid _PRS IRQ %d\n",
p->interrupts[i]);
continue;
}
link->irq.possible[i] = p->interrupts[i];
link->irq.possible_count++;
}
link->irq.triggering = p->triggering;
link->irq.polarity = p->polarity;
link->irq.resource_type = ACPI_RESOURCE_TYPE_EXTENDED_IRQ;
break;
}
default:
printk(KERN_ERR PREFIX "_PRS resource type 0x%x isn't an IRQ\n",
resource->type);
return AE_OK;
}
return AE_CTRL_TERMINATE;
}
static int acpi_pci_link_get_possible(struct acpi_pci_link *link)
{
acpi_status status;
status = acpi_walk_resources(link->device->handle, METHOD_NAME__PRS,
acpi_pci_link_check_possible, link);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "Evaluating _PRS"));
return -ENODEV;
}
ACPI_DEBUG_PRINT((ACPI_DB_INFO,
"Found %d possible IRQs\n",
link->irq.possible_count));
return 0;
}
static acpi_status acpi_pci_link_check_current(struct acpi_resource *resource,
void *context)
{
int *irq = context;