summaryrefslogblamecommitdiffstats
path: root/drivers/pci/hotplug/cpqphp_core.c
blob: b3659ffccac9c1ce5448df46247efcb2b6420468 (plain) (tree)
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279





























































                                                                                                     
                       









































































































































































































































































                                                                                             


                                                    
















                                                                  

                                                          

                                   

                                                                           
                                                            
                                        
                                        

                                                                     
 

                                                                      
                                                                    
                                        
                                          

                                                       
                                                                  


                                                                         

                                        




                                                         



                                                                            

                                                                               



                                                                             
                                             
 


                                                                    


                                                                       

                                                                  
 



                                                                       
                                                   
                                                                       
 

                                                                              

                                 


                                                                    
                                         

                                                                  
                                        

                                                                            

                                                                  



                                                                         
                






                                                                               
                
                                                             
                                                                         

                                                                       
                                             
                                                       
                             
                                                                              


                                        

                                        






                                  
           
                                  
           
                                 
             
                            
           
                    

















                                                                       

                                         







































































































































































































































































































































                                                                                           







                                                                               




                                                                                                

                                        






                                                                    

                                        











                                                                                           
                                                



                                                                                                        

                                                




                                                                                            

                                                

































































































































































































































































































































































































































                                                                                                              
                                         












                                                                                   

                                 







                              








































































































































































                                                                                   

                                          












































                                                                     
                                   












                                                     
                                 






                            
/*
 * 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/config.h>
#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/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 NULL;
	}
}


/**
 * get_SMBIOS_entry
 *
 * @type:SMBIOS structure type to be returned
 * @previous: %NULL or pointer to previously returned structure
 *
 * Gets the first entry of the specified type if previous == NULL
 * Otherwise, returns the next entry of the given type.
 * Uses global SMBIOS Table pointer
 * Uses get_subsequent_smbios_entry
 *
 * returns a pointer to an SMBIOS structure or %NULL if none found
 */
static void __iomem *get_SMBIOS_entry(void __iomem *smbios_start,
					void __iomem *smbios_table,
					u8 type,
					void __iomem *previous)
{
	if (!smbios_table)
		return NULL;

	if (!previous) {		  
		previous = smbios_start;
	} else {
		previous = get_subsequent_smbios_entry(smbios_start,
					smbios_table, previous);
	}

	while (previous) {
	       	if (readb(previous + SMBIOS_GENERIC_TYPE) != type) {
			previous = get_subsequent_smbios_entry(smbios_start,
						smbios_table, previous);
		} else {
			break;
		}
	}

	return previous;
}

static void release_slot(struct hotplug_slot *hotplug_slot)
{
	struct slot *slot = hotplug_slot->private;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	kfree(slot->hotplug_slot->info);
	kfree(slot->hotplug_slot->name);
	kfree(slot->hotplug_slot);
	kfree(slot);
}

static int ctrl_slot_setup(struct controller *ctrl,
			void __iomem *smbios_start,
			void __iomem *smbios_table)
{
	struct slot *slot;
	struct hotplug_slot *hotplug_slot;
	struct hotplug_slot_info *hotplug_slot_info;
	u8 number_of_slots;
	u8 slot_device;
	u8 slot_number;
	u8 ctrl_slot;
	u32 tempdword;
	void __iomem *slot_entry= NULL;
	int result = -ENOMEM;

	dbg("%s\n", __FUNCTION__);

	tempdword = readl(ctrl->hpc_reg + INT_INPUT_CLEAR);

	number_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F;
	slot_device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4;
	slot_number = ctrl->first_slot;

	while (number_of_slots) {
		slot = kmalloc(sizeof(*slot), GFP_KERNEL);
		if (!slot)
			goto error;

		memset(slot, 0, sizeof(struct slot));
		slot->hotplug_slot = kmalloc(sizeof(*(slot->hotplug_slot)),
						GFP_KERNEL);
		if (!slot->hotplug_slot)
			goto error_slot;
		hotplug_slot = slot->hotplug_slot;
		memset(hotplug_slot, 0, sizeof(struct hotplug_slot));

		hotplug_slot->info =
				kmalloc(sizeof(*(hotplug_slot->info)),
							GFP_KERNEL);
		if (!hotplug_slot->info)
			goto error_hpslot;
		hotplug_slot_info = hotplug_slot->info;
		memset(hotplug_slot_info, 0,
				sizeof(struct hotplug_slot_info));
		hotplug_slot->name = kmalloc(SLOT_NAME_SIZE, GFP_KERNEL);

		if (!hotplug_slot->name)
			goto error_info;

		slot->ctrl = ctrl;
		slot->bus = ctrl->bus;
		slot->device = slot_device;
		slot->number = slot_number;
		dbg("slot->number = %d\n", slot->number);

		slot_entry = get_SMBIOS_entry(smbios_start, smbios_table, 9,
					slot_entry);

		while (slot_entry && (readw(slot_entry + SMBIOS_SLOT_NUMBER) !=
				slot->number)) {
			slot_entry = get_SMBIOS_entry(smbios_start,
						smbios_table, 9, slot_entry);
		}

		slot->p_sm_slot = slot_entry;

		init_timer(&slot->task_event);
		slot->task_event.expires = jiffies + 5 * HZ;
		slot->task_event.function = cpqhp_pushbutton_thread;

		//FIXME: these capabilities aren't used but if they are
		//       they need to be correctly implemented
		slot->capabilities |= PCISLOT_REPLACE_SUPPORTED;
		slot->capabilities |= PCISLOT_INTERLOCK_SUPPORTED;

		if (is_slot64bit(slot))
			slot->capabilities |= PCISLOT_64_BIT_SUPPORTED;
		if (is_slot66mhz(slot))
			slot->capabilities |= PCISLOT_66_MHZ_SUPPORTED;
		if (ctrl->speed == PCI_SPEED_66MHz)
			slot->capabilities |= PCISLOT_66_MHZ_OPERATION;

		ctrl_slot =
			slot_device - (readb(ctrl->hpc_reg + SLOT_MASK) >> 4);

		// Check presence
		slot->capabilities |=
			((((~tempdword) >> 23) |
			 ((~tempdword) >> 15)) >> ctrl_slot) & 0x02;
		// Check the switch state
		slot->capabilities |=
			((~tempdword & 0xFF) >> ctrl_slot) & 0x01;
		// Check the slot enable
		slot->capabilities |=
			((read_slot_enable(ctrl) << 2) >> ctrl_slot) & 0x04;

		/* register this slot with the hotplug pci core */
		hotplug_slot->release = &release_slot;
		hotplug_slot->private = slot;
		make_slot_name(hotplug_slot->name, SLOT_NAME_SIZE, slot);
		hotplug_slot->ops = &cpqphp_hotplug_slot_ops;
		
		hotplug_slot_info->power_status = get_slot_enabled(ctrl, slot);
		hotplug_slot_info->attention_status =
			cpq_get_attention_status(ctrl, slot);
		hotplug_slot_info->latch_status =
			cpq_get_latch_status(ctrl, slot);
		hotplug_slot_info->adapter_status =
			get_presence_status(ctrl, slot);
		
		dbg("registering bus %d, dev %d, number %d, "
				"ctrl->slot_device_offset %d, slot %d\n",
				slot->bus, slot->device,
				slot->number, ctrl->slot_device_offset,
				slot_number);
		result = pci_hp_register(hotplug_slot);
		if (result) {
			err("pci_hp_register failed with error %d\n", result);
			goto error_name;
		}
		
		slot->next = ctrl->slot;
		ctrl->slot = slot;

		number_of_slots--;
		slot_device++;
		slot_number++;
	}

	return 0;
error_name:
	kfree(hotplug_slot->name);
error_info:
	kfree(hotplug_slot_info);
error_hpslot:
	kfree(hotplug_slot);
error_slot:
	kfree(slot);
error:
	return result;
}

static int ctrl_slot_cleanup (struct controller * ctrl)
{
	struct slot *old_slot, *next_slot;

	old_slot = ctrl->slot;
	ctrl->slot = NULL;

	while (old_slot) {
		/* memory will be freed by the release_slot callback */
		next_slot = old_slot->next;
		pci_hp_deregister (old_slot->hotplug_slot);
		old_slot = next_slot;
	}

	cpqhp_remove_debugfs_files(ctrl);

	//Free IRQ associated with hot plug device
	free_irq(ctrl->interrupt, ctrl);
	//Unmap the memory
	iounmap(ctrl->hpc_reg);
	//Finally reclaim PCI mem
	release_mem_region(pci_resource_start(ctrl->pci_dev, 0),
			   pci_resource_len(ctrl->pci_dev, 0));

	return(0);
}


//============================================================================
// function:	get_slot_mapping
//
// Description: Attempts to determine a logical slot mapping for a PCI
//		device.  Won't work for more than one PCI-PCI bridge
//		in a slot.
//
// Input:	u8 bus_num - bus number of PCI device
//		u8 dev_num - device number of PCI device
//		u8 *slot - Pointer to u8 where slot number will
//			be returned
//
// Output:	SUCCESS or FAILURE
//=============================================================================
static int
get_slot_mapping(struct pci_bus *bus, u8 bus_num, u8 dev_num, u8 *slot)
{
	struct irq_routing_table *PCIIRQRoutingInfoLength;
	u32 work;
	long len;
	long loop;

	u8 tbus, tdevice, tslot, bridgeSlot;

	dbg("%s: %p, %d, %d, %p\n", __FUNCTION__, bus, bus_num, dev_num, slot);

	bridgeSlot = 0xFF;

	PCIIRQRoutingInfoLength = pcibios_get_irq_routing_table();
	if (!PCIIRQRoutingInfoLength)
		return -1;

	len = (PCIIRQRoutingInfoLength->size -
	       sizeof(struct irq_routing_table)) / sizeof(struct irq_info);
	// Make sure I got at least one entry
	if (len == 0) {
		kfree(PCIIRQRoutingInfoLength);
		return -1;
	}

	for (loop = 0; loop < len; ++loop) {
		tbus = PCIIRQRoutingInfoLength->slots[loop].bus;
		tdevice = PCIIRQRoutingInfoLength->slots[loop].devfn >> 3;
		tslot = PCIIRQRoutingInfoLength->slots[loop].slot;

		if ((tbus == bus_num) && (tdevice == dev_num)) {
			*slot = tslot;
			kfree(PCIIRQRoutingInfoLength);
			return 0;
		} else {
			/* Did not get a match on the target PCI device. Check
			 * if the current IRQ table entry is a PCI-to-PCI bridge
			 * device.  If so, and it's secondary bus matches the
			 * bus number for the target device, I need to save the
			 * bridge's slot number.  If I can not find an entry for
			 * the target device, I will have to assume it's on the
			 * other side of the bridge, and assign it the bridge's
			 * slot. */
			bus->number = tbus;
			pci_bus_read_config_dword(bus, PCI_DEVFN(tdevice, 0),
						PCI_REVISION_ID, &work);

			if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) {
				pci_bus_read_config_dword(bus,
							PCI_DEVFN(tdevice, 0),
							PCI_PRIMARY_BUS, &work);
				// See if bridge's secondary bus matches target bus.
				if (((work >> 8) & 0x000000FF) == (long) bus_num) {
					bridgeSlot = tslot;
				}
			}
		}

	}

	// If we got here, we didn't find an entry in the IRQ mapping table 
	// for the target PCI device.  If we did determine that the target 
	// device is on the other side of a PCI-to-PCI bridge, return the 
	// slot number for the bridge.
	if (bridgeSlot != 0xFF) {
		*slot = bridgeSlot;
		kfree(PCIIRQRoutingInfoLength);
		return 0;
	}
	kfree(PCIIRQRoutingInfoLength);
	// Couldn't find an entry in the routing table for this PCI device
	return -1;
}


/**
 * cpqhp_set_attention_status - Turns the Amber LED for a slot on or off
 *
 */
static int
cpqhp_set_attention_status(struct controller *ctrl, struct pci_func *func,
				u32 status)
{
	u8 hp_slot;

	if (func == NULL)
		return(1);

	hp_slot = func->device - ctrl->slot_device_offset;

	// Wait for exclusive access to hardware
	down(&ctrl->crit_sect);

	if (status == 1) {
		amber_LED_on (ctrl, hp_slot);
	} else if (status == 0) {
		amber_LED_off (ctrl, hp_slot);
	} else {
		// Done with exclusive hardware access
		up(&ctrl->crit_sect);
		return(1);
	}

	set_SOGO(ctrl);

	// Wait for SOBS to be unset
	wait_for_ctrl_irq (ctrl);

	// Done with exclusive hardware access
	up(&ctrl->crit_sect);

	return(0);
}


/**
 * set_attention_status - Turns the Amber LED for a slot on or off
 *
 */
static int set_attention_status (struct hotplug_slot *hotplug_slot, u8 status)
{
	struct pci_func *slot_func;
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;
	u8 bus;
	u8 devfn;
	u8 device;
	u8 function;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1)
		return -ENODEV;

	device = devfn >> 3;
	function = devfn & 0x7;
	dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function);

	slot_func = cpqhp_slot_find(bus, device, function);
	if (!slot_func)
		return -ENODEV;

	return cpqhp_set_attention_status(ctrl, slot_func, status);
}


static int process_SI(struct hotplug_slot *hotplug_slot)
{
	struct pci_func *slot_func;
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;
	u8 bus;
	u8 devfn;
	u8 device;
	u8 function;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1)
		return -ENODEV;

	device = devfn >> 3;
	function = devfn & 0x7;
	dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function);

	slot_func = cpqhp_slot_find(bus, device, function);
	if (!slot_func)
		return -ENODEV;

	slot_func->bus = bus;
	slot_func->device = device;
	slot_func->function = function;
	slot_func->configured = 0;
	dbg("board_added(%p, %p)\n", slot_func, ctrl);
	return cpqhp_process_SI(ctrl, slot_func);
}


static int process_SS(struct hotplug_slot *hotplug_slot)
{
	struct pci_func *slot_func;
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;
	u8 bus;
	u8 devfn;
	u8 device;
	u8 function;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	if (cpqhp_get_bus_dev(ctrl, &bus, &devfn, slot->number) == -1)
		return -ENODEV;

	device = devfn >> 3;
	function = devfn & 0x7;
	dbg("bus, dev, fn = %d, %d, %d\n", bus, device, function);

	slot_func = cpqhp_slot_find(bus, device, function);
	if (!slot_func)
		return -ENODEV;

	dbg("In %s, slot_func = %p, ctrl = %p\n", __FUNCTION__, slot_func, ctrl);
	return cpqhp_process_SS(ctrl, slot_func);
}


static int hardware_test(struct hotplug_slot *hotplug_slot, u32 value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	return cpqhp_hardware_test(ctrl, value);	
}


static int get_power_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = get_slot_enabled(ctrl, slot);
	return 0;
}

static int get_attention_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;
	
	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = cpq_get_attention_status(ctrl, slot);
	return 0;
}

static int get_latch_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = cpq_get_latch_status(ctrl, slot);

	return 0;
}

static int get_adapter_status(struct hotplug_slot *hotplug_slot, u8 *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = get_presence_status(ctrl, slot);

	return 0;
}

static int get_max_bus_speed (struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = ctrl->speed_capability;

	return 0;
}

static int get_cur_bus_speed (struct hotplug_slot *hotplug_slot, enum pci_bus_speed *value)
{
	struct slot *slot = hotplug_slot->private;
	struct controller *ctrl = slot->ctrl;

	dbg("%s - physical_slot = %s\n", __FUNCTION__, hotplug_slot->name);

	*value = ctrl->speed;

	return 0;
}

static int cpqhpc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	u8 num_of_slots = 0;
	u8 hp_slot = 0;
	u8 device;
	u8 rev;
	u8 bus_cap;
	u16 temp_word;
	u16 vendor_id;
	u16 subsystem_vid;
	u16 subsystem_deviceid;
	u32 rc;
	struct controller *ctrl;
	struct pci_func *func;
	int err;

	err = pci_enable_device(pdev);
	if (err) {
		printk(KERN_ERR MY_NAME ": cannot enable PCI device %s (%d)\n",
			pci_name(pdev), err);
		return err;
	}

	// Need to read VID early b/c it's used to differentiate CPQ and INTC discovery
	rc = pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor_id);
	if (rc || ((vendor_id != PCI_VENDOR_ID_COMPAQ) && (vendor_id != PCI_VENDOR_ID_INTEL))) {
		err(msg_HPC_non_compaq_or_intel);
		rc = -ENODEV;
		goto err_disable_device;
	}
	dbg("Vendor ID: %x\n", vendor_id);

	rc = pci_read_config_byte(pdev, PCI_REVISION_ID, &rev);
	dbg("revision: %d\n", rev);
	if (rc || ((vendor_id == PCI_VENDOR_ID_COMPAQ) && (!rev))) {
		err(msg_HPC_rev_error);
		rc = -ENODEV;
		goto err_disable_device;
	}

	/* Check for the proper subsytem ID's
	 * Intel uses a different SSID programming model than Compaq.  
	 * For Intel, each SSID bit identifies a PHP capability.
	 * Also Intel HPC's may have RID=0.
	 */
	if ((rev > 2) || (vendor_id == PCI_VENDOR_ID_INTEL)) {
		// TODO: This code can be made to support non-Compaq or Intel subsystem IDs
		rc = pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vid);
		if (rc) {
			err("%s : pci_read_config_word failed\n", __FUNCTION__);
			goto err_disable_device;
		}
		dbg("Subsystem Vendor ID: %x\n", subsystem_vid);
		if ((subsystem_vid != PCI_VENDOR_ID_COMPAQ) && (subsystem_vid != PCI_VENDOR_ID_INTEL)) {
			err(msg_HPC_non_compaq_or_intel);
			rc = -ENODEV;
			goto err_disable_device;
		}

		ctrl = (struct controller *) kmalloc(sizeof(struct controller), GFP_KERNEL);
		if (!ctrl) {
			err("%s : out of memory\n", __FUNCTION__);
			rc = -ENOMEM;
			goto err_disable_device;
		}
		memset(ctrl, 0, sizeof(struct controller));

		rc = pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &subsystem_deviceid);
		if (rc) {
			err("%s : pci_read_config_word failed\n", __FUNCTION__);
			goto err_free_ctrl;
		}

		info("Hot Plug Subsystem Device ID: %x\n", subsystem_deviceid);

		/* Set Vendor ID, so it can be accessed later from other functions */
		ctrl->vendor_id = vendor_id;

		switch (subsystem_vid) {
			case PCI_VENDOR_ID_COMPAQ:
				if (rev >= 0x13) { /* CIOBX */
					ctrl->push_flag = 1;
					ctrl->slot_switch_type = 1;
					ctrl->push_button = 1;
					ctrl->pci_config_space = 1;
					ctrl->defeature_PHP = 1;
					ctrl->pcix_support = 1;
					ctrl->pcix_speed_capability = 1;
					pci_read_config_byte(pdev, 0x41, &bus_cap);
					if (bus_cap & 0x80) {
						dbg("bus max supports 133MHz PCI-X\n");
						ctrl->speed_capability = PCI_SPEED_133MHz_PCIX;
						break;
					}
					if (bus_cap & 0x40) {
						dbg("bus max supports 100MHz PCI-X\n");
						ctrl->speed_capability = PCI_SPEED_100MHz_PCIX;
						break;
					}
					if (bus_cap & 20) {
						dbg("bus max supports 66MHz PCI-X\n");
						ctrl->speed_capability = PCI_SPEED_66MHz_PCIX;
						break;
					}
					if (bus_cap & 10) {
						dbg("bus max supports 66MHz PCI\n");
						ctrl->speed_capability = PCI_SPEED_66MHz;
						break;
					}

					break;
				}

				switch (subsystem_deviceid) {
					case PCI_SUB_HPC_ID:
						/* Original 6500/7000 implementation */
						ctrl->slot_switch_type = 1;
						ctrl->speed_capability = PCI_SPEED_33MHz;
						ctrl->push_button = 0;
						ctrl->pci_config_space = 1;
						ctrl->defeature_PHP = 1;
						ctrl->pcix_support = 0;
						ctrl->pcix_speed_capability = 0;
						break;
					case PCI_SUB_HPC_ID2:
						/* First Pushbutton implementation */
						ctrl->push_flag = 1;
						ctrl->slot_switch_type = 1;
						ctrl->speed_capability = PCI_SPEED_33MHz;
						ctrl->push_button = 1;
						ctrl->pci_config_space = 1;
						ctrl->defeature_PHP = 1;
						ctrl->pcix_support = 0;
						ctrl->pcix_speed_capability = 0;
						break;
					case PCI_SUB_HPC_ID_INTC:
						/* Third party (6500/7000) */
						ctrl->slot_switch_type = 1;
						ctrl->speed_capability = PCI_SPEED_33MHz;
						ctrl->push_button = 0;
						ctrl->pci_config_space = 1;
						ctrl->defeature_PHP = 1;
						ctrl->pcix_support = 0;
						ctrl->pcix_speed_capability = 0;
						break;
					case PCI_SUB_HPC_ID3:
						/* First 66 Mhz implementation */
						ctrl->push_flag = 1;
						ctrl->slot_switch_type = 1;
						ctrl->speed_capability = PCI_SPEED_66MHz;
						ctrl->push_button = 1;
						ctrl->pci_config_space = 1;
						ctrl->defeature_PHP = 1;
						ctrl->pcix_support = 0;
						ctrl->pcix_speed_capability = 0;
						break;
					case PCI_SUB_HPC_ID4:
						/* First PCI-X implementation, 100MHz */
						ctrl->push_flag = 1;
						ctrl->slot_switch_type = 1;
						ctrl->speed_capability = PCI_SPEED_100MHz_PCIX;
						ctrl->push_button = 1;
						ctrl->pci_config_space = 1;
						ctrl->defeature_PHP = 1;
						ctrl->pcix_support = 1;
						ctrl->pcix_speed_capability = 0;	
						break;
					default:
						err(msg_HPC_not_supported);
						rc = -ENODEV;
						goto err_free_ctrl;
				}
				break;

			case PCI_VENDOR_ID_INTEL:
				/* Check for speed capability (0=33, 1=66) */
				if (subsystem_deviceid & 0x0001) {
					ctrl->speed_capability = PCI_SPEED_66MHz;
				} else {
					ctrl->speed_capability = PCI_SPEED_33MHz;
				}

				/* Check for push button */
				if (subsystem_deviceid & 0x0002) {
					/* no push button */
					ctrl->push_button = 0;
				} else {
					/* push button supported */
					ctrl->push_button = 1;
				}

				/* Check for slot switch type (0=mechanical, 1=not mechanical) */
				if (subsystem_deviceid & 0x0004) {
					/* no switch */
					ctrl->slot_switch_type = 0;
				} else {
					/* switch */
					ctrl->slot_switch_type = 1;
				}

				/* PHP Status (0=De-feature PHP, 1=Normal operation) */
				if (subsystem_deviceid & 0x0008) {
					ctrl->defeature_PHP = 1;	// PHP supported
				} else {
					ctrl->defeature_PHP = 0;	// PHP not supported
				}

				/* Alternate Base Address Register Interface (0=not supported, 1=supported) */
				if (subsystem_deviceid & 0x0010) {
					ctrl->alternate_base_address = 1;	// supported
				} else {
					ctrl->alternate_base_address = 0;	// not supported
				}

				/* PCI Config Space Index (0=not supported, 1=supported) */
				if (subsystem_deviceid & 0x0020) {
					ctrl->pci_config_space = 1;		// supported
				} else {
					ctrl->pci_config_space = 0;		// not supported
				}

				/* PCI-X support */
				if (subsystem_deviceid & 0x0080) {
					/* PCI-X capable */
					ctrl->pcix_support = 1;
					/* Frequency of operation in PCI-X mode */
					if (subsystem_deviceid & 0x0040) {
						/* 133MHz PCI-X if bit 7 is 1 */
						ctrl->pcix_speed_capability = 1;
					} else {
						/* 100MHz PCI-X if bit 7 is 1 and bit 0 is 0, */
						/* 66MHz PCI-X if bit 7 is 1 and bit 0 is 1 */
						ctrl->pcix_speed_capability = 0;
					}
				} else {
					/* Conventional PCI */
					ctrl->pcix_support = 0;
					ctrl->pcix_speed_capability = 0;
				}
				break;

			default:
				err(msg_HPC_not_supported);
				rc = -ENODEV;
				goto err_free_ctrl;
		}

	} else {
		err(msg_HPC_not_supported);
		return -ENODEV;
	}

	// Tell the user that we found one.
	info("Initializing the PCI hot plug controller residing on PCI bus %d\n",
					pdev->bus->number);

	dbg("Hotplug controller capabilities:\n");
	dbg("    speed_capability       %d\n", ctrl->speed_capability);
	dbg("    slot_switch_type       %s\n", ctrl->slot_switch_type ?
					"switch present" : "no switch");
	dbg("    defeature_PHP          %s\n", ctrl->defeature_PHP ?
					"PHP supported" : "PHP not supported");
	dbg("    alternate_base_address %s\n", ctrl->alternate_base_address ?
					"supported" : "not supported");
	dbg("    pci_config_space       %s\n", ctrl->pci_config_space ?
					"supported" : "not supported");
	dbg("    pcix_speed_capability  %s\n", ctrl->pcix_speed_capability ?
					"supported" : "not supported");
	dbg("    pcix_support           %s\n", ctrl->pcix_support ?
					"supported" : "not supported");

	ctrl->pci_dev = pdev;
	pci_set_drvdata(pdev, ctrl);

	/* make our own copy of the pci bus structure,
	 * as we like tweaking it a lot */
	ctrl->pci_bus = kmalloc(sizeof(*ctrl->pci_bus), GFP_KERNEL);
	if (!ctrl->pci_bus) {
		err("out of memory\n");
		rc = -ENOMEM;
		goto err_free_ctrl;
	}
	memcpy(ctrl->pci_bus, pdev->bus, sizeof(*ctrl->pci_bus));

	ctrl->bus = pdev->bus->number;
	ctrl->rev = rev;
	dbg("bus device function rev: %d %d %d %d\n", ctrl->bus,
		PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), ctrl->rev);

	init_MUTEX(&ctrl->crit_sect);
	init_waitqueue_head(&ctrl->queue);

	/* initialize our threads if they haven't already been started up */
	rc = one_time_init();
	if (rc) {
		goto err_free_bus;
	}
	
	dbg("pdev = %p\n", pdev);
	dbg("pci resource start %lx\n", pci_resource_start(pdev, 0));
	dbg("pci resource len %lx\n", pci_resource_len(pdev, 0));

	if (!request_mem_region(pci_resource_start(pdev, 0),
				pci_resource_len(pdev, 0), MY_NAME)) {
		err("cannot reserve MMIO region\n");
		rc = -ENOMEM;
		goto err_free_bus;
	}

	ctrl->hpc_reg = ioremap(pci_resource_start(pdev, 0),
					pci_resource_len(pdev, 0));
	if (!ctrl->hpc_reg) {
		err("cannot remap MMIO region %lx @ %lx\n",
				pci_resource_len(pdev, 0),
				pci_resource_start(pdev, 0));
		rc = -ENODEV;
		goto err_free_mem_region;
	}

	// Check for 66Mhz operation
	ctrl->speed = get_controller_speed(ctrl);


	/********************************************************
	 *
	 *              Save configuration headers for this and
	 *              subordinate PCI buses
	 *
	 ********************************************************/

	// find the physical slot number of the first hot plug slot

	/* Get slot won't work for devices behind bridges, but
	 * in this case it will always be called for the "base"
	 * bus/dev/func of a slot.
	 * CS: this is leveraging the PCIIRQ routing code from the kernel
	 * (pci-pc.c: get_irq_routing_table) */
	rc = get_slot_mapping(ctrl->pci_bus, pdev->bus->number,
				(readb(ctrl->hpc_reg + SLOT_MASK) >> 4),
				&(ctrl->first_slot));
	dbg("get_slot_mapping: first_slot = %d, returned = %d\n",
				ctrl->first_slot, rc);
	if (rc) {
		err(msg_initialization_err, rc);
		goto err_iounmap;
	}

	// Store PCI Config Space for all devices on this bus
	rc = cpqhp_save_config(ctrl, ctrl->bus, readb(ctrl->hpc_reg + SLOT_MASK));
	if (rc) {
		err("%s: unable to save PCI configuration data, error %d\n",
				__FUNCTION__, rc);
		goto err_iounmap;
	}

	/*
	 * Get IO, memory, and IRQ resources for new devices
	 */
	// The next line is required for cpqhp_find_available_resources
	ctrl->interrupt = pdev->irq;
	if (ctrl->interrupt < 0x10) {
		cpqhp_legacy_mode = 1;
		dbg("System seems to be configured for Full Table Mapped MPS mode\n");
	}

	ctrl->cfgspc_irq = 0;
	pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &ctrl->cfgspc_irq);

	rc = cpqhp_find_available_resources(ctrl, cpqhp_rom_start);
	ctrl->add_support = !rc;
	if (rc) {
		dbg("cpqhp_find_available_resources = 0x%x\n", rc);
		err("unable to locate PCI configuration resources for hot plug add.\n");
		goto err_iounmap;
	}

	/*
	 * Finish setting up the hot plug ctrl device
	 */
	ctrl->slot_device_offset = readb(ctrl->hpc_reg + SLOT_MASK) >> 4;
	dbg("NumSlots %d \n", ctrl->slot_device_offset);

	ctrl->next_event = 0;

	/* Setup the slot information structures */
	rc = ctrl_slot_setup(ctrl, smbios_start, smbios_table);
	if (rc) {
		err(msg_initialization_err, 6);
		err("%s: unable to save PCI configuration data, error %d\n",
			__FUNCTION__, rc);
		goto err_iounmap;
	}
	
	/* Mask all general input interrupts */
	writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_MASK);

	/* set up the interrupt */
	dbg("HPC interrupt = %d \n", ctrl->interrupt);
	if (request_irq(ctrl->interrupt, cpqhp_ctrl_intr,
			SA_SHIRQ, MY_NAME, ctrl)) {
		err("Can't get irq %d for the hotplug pci controller\n",
			ctrl->interrupt);
		rc = -ENODEV;
		goto err_iounmap;
	}

	/* Enable Shift Out interrupt and clear it, also enable SERR on power fault */
	temp_word = readw(ctrl->hpc_reg + MISC);
	temp_word |= 0x4006;
	writew(temp_word, ctrl->hpc_reg + MISC);

	// Changed 05/05/97 to clear all interrupts at start
	writel(0xFFFFFFFFL, ctrl->hpc_reg + INT_INPUT_CLEAR);

	ctrl->ctrl_int_comp = readl(ctrl->hpc_reg + INT_INPUT_CLEAR);

	writel(0x0L, ctrl->hpc_reg + INT_MASK);

	if (!cpqhp_ctrl_list) {
		cpqhp_ctrl_list = ctrl;
		ctrl->next = NULL;
	} else {
		ctrl->next = cpqhp_ctrl_list;
		cpqhp_ctrl_list = ctrl;
	}

	// turn off empty slots here unless command line option "ON" set
	// Wait for exclusive access to hardware
	down(&ctrl->crit_sect);

	num_of_slots = readb(ctrl->hpc_reg + SLOT_MASK) & 0x0F;

	// find first device number for the ctrl
	device = readb(ctrl->hpc_reg + SLOT_MASK) >> 4;

	while (num_of_slots) {
		dbg("num_of_slots: %d\n", num_of_slots);
		func = cpqhp_slot_find(ctrl->bus, device, 0);
		if (!func)
			break;

		hp_slot = func->device - ctrl->slot_device_offset;
		dbg("hp_slot: %d\n", hp_slot);

		// We have to save the presence info for these slots
		temp_word = ctrl->ctrl_int_comp >> 16;
		func->presence_save = (temp_word >> hp_slot) & 0x01;
		func->presence_save |= (temp_word >> (hp_slot + 7)) & 0x02;

		if (ctrl->ctrl_int_comp & (0x1L << hp_slot)) {
			func->switch_save = 0;
		} else {
			func->switch_save = 0x10;
		}

		if (!power_mode) {
			if (!func->is_a_board) {
				green_LED_off(ctrl, hp_slot);
				slot_disable(ctrl, hp_slot);
			}
		}

		device++;
		num_of_slots--;
	}

	if (!power_mode) {
		set_SOGO(ctrl);
		// Wait for SOBS to be unset
		wait_for_ctrl_irq(ctrl);
	}

	rc = init_SERR(ctrl);
	if (rc) {
		err("init_SERR failed\n");
		up(&ctrl->crit_sect);
		goto err_free_irq;
	}

	// Done with exclusive hardware access
	up(&ctrl->crit_sect);

	cpqhp_create_debugfs_files(ctrl);

	return 0;

err_free_irq:
	free_irq(ctrl->interrupt, ctrl);
err_iounmap:
	iounmap(ctrl->hpc_reg);
err_free_mem_region:
	release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
err_free_bus:
	kfree(ctrl->pci_bus);
err_free_ctrl:
	kfree(ctrl);
err_disable_device:
	pci_disable_device(pdev);
	return rc;
}


static int one_time_init(void)
{
	int loop;
	int retval = 0;

	if (initialized)
		return 0;

	power_mode = 0;

	retval = pci_print_IRQ_route();
	if (retval)
		goto error;

	dbg("Initialize + Start the notification mechanism \n");

	retval = cpqhp_event_start_thread();
	if (retval)
		goto error;

	dbg("Initialize slot lists\n");
	for (loop = 0; loop < 256; loop++) {
		cpqhp_slot_list[loop] = NULL;
	}

	// FIXME: We also need to hook the NMI handler eventually.
	// this also needs to be worked with Christoph
	// register_NMI_handler();

	// Map rom address
	cpqhp_rom_start = ioremap(ROM_PHY_ADDR, ROM_PHY_LEN);
	if (!cpqhp_rom_start) {
		err ("Could not ioremap memory region for ROM\n");
		retval = -EIO;
		goto error;
	}
	
	/* Now, map the int15 entry point if we are on compaq specific hardware */
	compaq_nvram_init(cpqhp_rom_start);
	
	/* Map smbios table entry point structure */
	smbios_table = detect_SMBIOS_pointer(cpqhp_rom_start,
					cpqhp_rom_start + ROM_PHY_LEN);
	if (!smbios_table) {
		err ("Could not find the SMBIOS pointer in memory\n");
		retval = -EIO;
		goto error_rom_start;
	}

	smbios_start = ioremap(readl(smbios_table + ST_ADDRESS),
					readw(smbios_table + ST_LENGTH));
	if (!smbios_start) {
		err ("Could not ioremap memory region taken from SMBIOS values\n");
		retval = -EIO;
		goto error_smbios_start;
	}

	initialized = 1;

	return retval;

error_smbios_start:
	iounmap(smbios_start);
error_rom_start:
	iounmap(cpqhp_rom_start);
error:
	return retval;
}


static void __exit unload_cpqphpd(void)
{
	struct pci_func *next;
	struct pci_func *TempSlot;
	int loop;
	u32 rc;
	struct controller *ctrl;
	struct controller *tctrl;
	struct pci_resource *res;
	struct pci_resource *tres;

	rc = compaq_nvram_store(cpqhp_rom_start);

	ctrl = cpqhp_ctrl_list;

	while (ctrl) {
		if (ctrl->hpc_reg) {
			u16 misc;
			rc = read_slot_enable (ctrl);
			
			writeb(0, ctrl->hpc_reg + SLOT_SERR);
			writel(0xFFFFFFC0L | ~rc, ctrl->hpc_reg + INT_MASK);
			
			misc = readw(ctrl->hpc_reg + MISC);
			misc &= 0xFFFD;
			writew(misc, ctrl->hpc_reg + MISC);
		}

		ctrl_slot_cleanup(ctrl);

		res = ctrl->io_head;
		while (res) {
			tres = res;
			res = res->next;
			kfree(tres);
		}

		res = ctrl->mem_head;
		while (res) {
			tres = res;
			res = res->next;
			kfree(tres);
		}

		res = ctrl->p_mem_head;
		while (res) {
			tres = res;
			res = res->next;
			kfree(tres);
		}

		res = ctrl->bus_head;
		while (res) {
			tres = res;
			res = res->next;
			kfree(tres);
		}

		kfree (ctrl->pci_bus);

		tctrl = ctrl;
		ctrl = ctrl->next;
		kfree(tctrl);
	}

	for (loop = 0; loop < 256; loop++) {
		next = cpqhp_slot_list[loop];
		while (next != NULL) {
			res = next->io_head;
			while (res) {
				tres = res;
				res = res->next;
				kfree(tres);
			}

			res = next->mem_head;
			while (res) {
				tres = res;
				res = res->next;
				kfree(tres);
			}

			res = next->p_mem_head;
			while (res) {
				tres = res;
				res = res->next;
				kfree(tres);
			}

			res = next->bus_head;
			while (res) {
				tres = res;
				res = res->next;
				kfree(tres);
			}

			TempSlot = next;
			next = next->next;
			kfree(TempSlot);
		}
	}

	// Stop the notification mechanism
	if (initialized)
		cpqhp_event_stop_thread();

	//unmap the rom address
	if (cpqhp_rom_start)
		iounmap(cpqhp_rom_start);
	if (smbios_start)
		iounmap(smbios_start);
}



static struct pci_device_id hpcd_pci_tbl[] = {
	{
	/* handle any PCI Hotplug controller */
	.class =        ((PCI_CLASS_SYSTEM_PCI_HOTPLUG << 8) | 0x00),
	.class_mask =   ~0,
	
	/* no matter who makes it */
	.vendor =       PCI_ANY_ID,
	.device =       PCI_ANY_ID,
	.subvendor =    PCI_ANY_ID,
	.subdevice =    PCI_ANY_ID,
	
	}, { /* end: all zeroes */ }
};

MODULE_DEVICE_TABLE(pci, hpcd_pci_tbl);



static struct pci_driver cpqhpc_driver = {
	.name =		"compaq_pci_hotplug",
	.id_table =	hpcd_pci_tbl,
	.probe =	cpqhpc_probe,
	/* remove:	cpqhpc_remove_one, */
};



static int __init cpqhpc_init(void)
{
	int result;

	cpqhp_debug = debug;

	info (DRIVER_DESC " version: " DRIVER_VERSION "\n");
	cpqhp_initialize_debugfs();
	result = pci_register_driver(&cpqhpc_driver);
	dbg("pci_register_driver = %d\n", result);
	return result;
}


static void __exit cpqhpc_cleanup(void)
{
	dbg("unload_cpqphpd()\n");
	unload_cpqphpd();

	dbg("pci_unregister_driver\n");
	pci_unregister_driver(&cpqhpc_driver);
	cpqhp_shutdown_debugfs();
}


module_init(cpqhpc_init);
module_exit(cpqhpc_cleanup);