/*
* Compaq Hot Plug Controller Driver
*
* Copyright (C) 1995,2001 Compaq Computer Corporation
* Copyright (C) 2001 Greg Kroah-Hartman <greg@kroah.com>
* Copyright (C) 2001 IBM Corp.
*
* 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 <greg@kroah.com>
*
* Jan 12, 2003 - Added 66/100/133MHz PCI-X support,
* Torben Mathiasen <torben.mathiasen@hp.com>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include "cpqphp.h"
#include "cpqphp_nvram.h"
#include "../../../arch/i386/pci/pci.h" /* horrible hack showing how processor dependent we are... */
/* Global variables */
int cpqhp_debug;
int cpqhp_legacy_mode;
struct controller *cpqhp_ctrl_list; /* = NULL */
struct pci_func *cpqhp_slot_list[256];
/* local variables */
static void __iomem *smbios_table;
static void __iomem *smbios_start;
static void __iomem *cpqhp_rom_start;
static int power_mode;
static int debug;
static int initialized;
#define DRIVER_VERSION "0.9.8"
#define DRIVER_AUTHOR "Dan Zink <dan.zink@compaq.com>, Greg Kroah-Hartman <greg@kroah.com>"
#define DRIVER_DESC "Compaq Hot Plug PCI Controller Driver"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
module_param(power_mode, bool, 0644);
MODULE_PARM_DESC(power_mode, "Power mode enabled or not");
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, "Debugging mode enabled or not");
#define CPQHPC_MODULE_MINOR 208
static int one_time_init (void);
static int set_attention_status (struct hotplug_slot *slot, u8 value);
static int process_SI (struct hotplug_slot *slot);
static int process_SS (struct hotplug_slot *slot);
static int hardware_test (struct hotplug_slot *slot, u32 value);
static int get_power_status (struct hotplug_slot *slot, u8 *value);
static int get_attention_status (struct hotplug_slot *slot, u8 *value);
static int get_latch_status (struct hotplug_slot *slot, u8 *value);
static int get_adapter_status (struct hotplug_slot *slot, u8 *value);
static int get_max_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value);
static int get_cur_bus_speed (struct hotplug_slot *slot, enum pci_bus_speed *value);
static struct hotplug_slot_ops cpqphp_hotplug_slot_ops = {
.owner = THIS_MODULE,
.set_attention_status = set_attention_status,
.enable_slot = process_SI,
.disable_slot = process_SS,
.hardware_test = hardware_test,
.get_power_status = get_power_status,
.get_attention_status = get_attention_status,
.get_latch_status = get_latch_status,
.get_adapter_status = get_adapter_status,
.get_max_bus_speed = get_max_bus_speed,
.get_cur_bus_speed = get_cur_bus_speed,
};
static inline int is_slot64bit(struct slot *slot)
{
return (readb(slot->p_sm_slot + SMBIOS_SLOT_WIDTH) == 0x06) ? 1 : 0;
}
static inline int is_slot66mhz(struct slot *slot)
{
return (readb(slot->p_sm_slot + SMBIOS_SLOT_TYPE) == 0x0E) ? 1 : 0;
}
/**
* detect_SMBIOS_pointer - find the System Management BIOS Table in mem region.
*
* @begin: begin pointer for region to be scanned.
* @end: end pointer for region to be scanned.
*
* Returns pointer to the head of the SMBIOS tables (or NULL)
*
*/
static void __iomem * detect_SMBIOS_pointer(void __iomem *begin, void __iomem *end)
{
void __iomem *fp;
void __iomem *endp;
u8 temp1, temp2, temp3, temp4;
int status = 0;
endp = (end - sizeof(u32) + 1);
for (fp = begin; fp <= endp; fp += 16) {
temp1 = readb(fp);
temp2 = readb(fp+1);
temp3 = readb(fp+2);
temp4 = readb(fp+3);
if (temp1 == '_' &&
temp2 == 'S' &&
temp3 == 'M' &&
temp4 == '_') {
status = 1;
break;
}
}
if (!status)
fp = NULL;
dbg("Discovered SMBIOS Entry point at %p\n", fp);
return fp;
}
/**
* init_SERR - Initializes the per slot SERR generation.
*
* For unexpected switch opens
*
*/
static int init_SERR(struct controller * ctrl)
{
u32 tempdword;
u32 number_of_slots;
u8 physical_slot;
if (!ctrl)
return 1;
tempdword = ctrl->first_slot;
number_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F;
// Loop through slots
while (number_of_slots) {
physical_slot = tempdword;
writeb(0, ctrl->hpc_reg + SLOT_SERR);
tempdword++;
number_of_slots--;
}
return 0;
}
/* nice debugging output */
static int pci_print_IRQ_route (void)
{
struct irq_routing_table *routing_table;
int len;
int loop;
u8 tbus, tdevice, tslot;
routing_table = pcibios_get_irq_routing_table();
if (routing_table == NULL) {
err("No BIOS Routing Table??? Not good\n");
return -ENOMEM;
}
len = (routing_table->size - sizeof(struct irq_routing_table)) /
sizeof(struct irq_info);
// Make sure I got at least one entry
if (len == 0) {
kfree(routing_table);
return -1;
}
dbg("bus dev func slot\n");
for (loop = 0; loop < len; ++loop) {
tbus = routing_table->slots[loop].bus;
tdevice = routing_table->slots[loop].devfn;
tslot = routing_table->slots[loop].slot;
dbg("%d %d %d %d\n", tbus, tdevice >> 3, tdevice & 0x7, tslot);
}
kfree(routing_table);
return 0;
}
/**
* get_subsequent_smbios_entry: get the next entry from bios table.
*
* Gets the first entry if previous == NULL
* Otherwise, returns the next entry
* Uses global SMBIOS Table pointer
*
* @curr: %NULL or pointer to previously returned structure
*
* returns a pointer to an SMBIOS structure or NULL if none found
*/
static void __iomem *get_subsequent_smbios_entry(void __iomem *smbios_start,
void __iomem *smbios_table,
void __iomem *curr)
{
u8 bail = 0;
u8 previous_byte = 1;
void __iomem *p_temp;
void __iomem *p_max;
if (!smbios_table || !curr)
return(NULL);
// set p_max to the end of the table
p_max = smbios_start + readw(smbios_table + ST_LENGTH);
p_temp = curr;
p_temp += readb(curr + SMBIOS_GENERIC_LENGTH);
while ((p_temp < p_max) && !bail) {
/* Look for the double NULL terminator
* The first condition is the previous byte
* and the second is the curr */
if (!previous_byte && !(readb(p_temp))) {
bail = 1;
}
previous_byte = readb(p_temp);
p_temp++;
}
if (p_temp < p_max) {
return p_temp;
} else {
return NUL
|