mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	PCIEHP: Add Electro Mechanical Interlock (EMI) support to the PCIE hotplug driver.
Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
		
							parent
							
								
									262303fe32
								
							
						
					
					
						commit
						34d03419f0
					
				
					 3 changed files with 163 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -69,6 +69,7 @@ struct slot {
 | 
			
		|||
	struct hotplug_slot *hotplug_slot;
 | 
			
		||||
	struct list_head	slot_list;
 | 
			
		||||
	char name[SLOT_NAME_SIZE];
 | 
			
		||||
	unsigned long last_emi_toggle;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct event_info {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,6 +139,7 @@ struct controller {
 | 
			
		|||
#define ATTN_LED_PRSN	0x00000008
 | 
			
		||||
#define PWR_LED_PRSN	0x00000010
 | 
			
		||||
#define HP_SUPR_RM_SUP	0x00000020
 | 
			
		||||
#define EMI_PRSN	0x00020000
 | 
			
		||||
 | 
			
		||||
#define ATTN_BUTTN(cap)		(cap & ATTN_BUTTN_PRSN)
 | 
			
		||||
#define POWER_CTRL(cap)		(cap & PWR_CTRL_PRSN)
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +147,7 @@ struct controller {
 | 
			
		|||
#define ATTN_LED(cap)		(cap & ATTN_LED_PRSN)
 | 
			
		||||
#define PWR_LED(cap)		(cap & PWR_LED_PRSN) 
 | 
			
		||||
#define HP_SUPR_RM(cap)		(cap & HP_SUPR_RM_SUP)
 | 
			
		||||
#define EMI(cap)		(cap & EMI_PRSN)
 | 
			
		||||
 | 
			
		||||
extern int pciehp_event_start_thread(void);
 | 
			
		||||
extern void pciehp_event_stop_thread(void);
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +185,8 @@ struct hpc_ops {
 | 
			
		|||
	int (*set_attention_status)(struct slot *slot, u8 status);
 | 
			
		||||
	int (*get_latch_status)(struct slot *slot, u8 *status);
 | 
			
		||||
	int (*get_adapter_status)(struct slot *slot, u8 *status);
 | 
			
		||||
	int (*get_emi_status)(struct slot *slot, u8 *status);
 | 
			
		||||
	int (*toggle_emi)(struct slot *slot);
 | 
			
		||||
	int (*get_max_bus_speed)(struct slot *slot, enum pci_bus_speed *speed);
 | 
			
		||||
	int (*get_cur_bus_speed)(struct slot *slot, enum pci_bus_speed *speed);
 | 
			
		||||
	int (*get_max_lnk_width)(struct slot *slot, enum pcie_link_width *val);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
#include <linux/pci.h>
 | 
			
		||||
#include "pciehp.h"
 | 
			
		||||
#include <linux/interrupt.h>
 | 
			
		||||
#include <linux/time.h>
 | 
			
		||||
 | 
			
		||||
/* Global variables */
 | 
			
		||||
int pciehp_debug;
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +88,95 @@ static struct hotplug_slot_ops pciehp_hotplug_slot_ops = {
 | 
			
		|||
  	.get_cur_bus_speed =	get_cur_bus_speed,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Check the status of the Electro Mechanical Interlock (EMI)
 | 
			
		||||
 */
 | 
			
		||||
static int get_lock_status(struct hotplug_slot *hotplug_slot, u8 *value)
 | 
			
		||||
{
 | 
			
		||||
	struct slot *slot = hotplug_slot->private;
 | 
			
		||||
	return (slot->hpc_ops->get_emi_status(slot, value));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * sysfs interface for the Electro Mechanical Interlock (EMI)
 | 
			
		||||
 * 1 == locked, 0 == unlocked
 | 
			
		||||
 */
 | 
			
		||||
static ssize_t lock_read_file(struct hotplug_slot *slot, char *buf)
 | 
			
		||||
{
 | 
			
		||||
	int retval;
 | 
			
		||||
	u8 value;
 | 
			
		||||
 | 
			
		||||
	retval = get_lock_status(slot, &value);
 | 
			
		||||
	if (retval)
 | 
			
		||||
		goto lock_read_exit;
 | 
			
		||||
	retval = sprintf (buf, "%d\n", value);
 | 
			
		||||
 | 
			
		||||
lock_read_exit:
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Change the status of the Electro Mechanical Interlock (EMI)
 | 
			
		||||
 * This is a toggle - in addition there must be at least 1 second
 | 
			
		||||
 * in between toggles.
 | 
			
		||||
 */
 | 
			
		||||
static int set_lock_status(struct hotplug_slot *hotplug_slot, u8 status)
 | 
			
		||||
{
 | 
			
		||||
	struct slot *slot = hotplug_slot->private;
 | 
			
		||||
	int retval;
 | 
			
		||||
	u8 value;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&slot->ctrl->crit_sect);
 | 
			
		||||
 | 
			
		||||
	/* has it been >1 sec since our last toggle? */
 | 
			
		||||
	if ((get_seconds() - slot->last_emi_toggle) < 1)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	/* see what our current state is */
 | 
			
		||||
	retval = get_lock_status(hotplug_slot, &value);
 | 
			
		||||
	if (retval || (value == status))
 | 
			
		||||
		goto set_lock_exit;
 | 
			
		||||
 | 
			
		||||
	slot->hpc_ops->toggle_emi(slot);
 | 
			
		||||
set_lock_exit:
 | 
			
		||||
	mutex_unlock(&slot->ctrl->crit_sect);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * sysfs interface which allows the user to toggle the Electro Mechanical
 | 
			
		||||
 * Interlock.  Valid values are either 0 or 1.  0 == unlock, 1 == lock
 | 
			
		||||
 */
 | 
			
		||||
static ssize_t lock_write_file(struct hotplug_slot *slot, const char *buf,
 | 
			
		||||
		size_t count)
 | 
			
		||||
{
 | 
			
		||||
	unsigned long llock;
 | 
			
		||||
	u8 lock;
 | 
			
		||||
	int retval = 0;
 | 
			
		||||
 | 
			
		||||
	llock = simple_strtoul(buf, NULL, 10);
 | 
			
		||||
	lock = (u8)(llock & 0xff);
 | 
			
		||||
 | 
			
		||||
	switch (lock) {
 | 
			
		||||
		case 0:
 | 
			
		||||
		case 1:
 | 
			
		||||
			retval = set_lock_status(slot, lock);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			err ("%d is an invalid lock value\n", lock);
 | 
			
		||||
			retval = -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	if (retval)
 | 
			
		||||
		return retval;
 | 
			
		||||
	return count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct hotplug_slot_attribute hotplug_slot_attr_lock = {
 | 
			
		||||
	.attr = {.name = "lock", .mode = S_IFREG | S_IRUGO | S_IWUSR},
 | 
			
		||||
	.show = lock_read_file,
 | 
			
		||||
	.store = lock_write_file
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * release_slot - free up the memory used by a slot
 | 
			
		||||
 * @hotplug_slot: slot to free
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +249,16 @@ static int init_slots(struct controller *ctrl)
 | 
			
		|||
			err ("pci_hp_register failed with error %d\n", retval);
 | 
			
		||||
			goto error_info;
 | 
			
		||||
		}
 | 
			
		||||
		/* create additional sysfs entries */
 | 
			
		||||
		if (EMI(ctrl->ctrlcap)) {
 | 
			
		||||
			retval = sysfs_create_file(&hotplug_slot->kobj,
 | 
			
		||||
				&hotplug_slot_attr_lock.attr);
 | 
			
		||||
			if (retval) {
 | 
			
		||||
				pci_hp_deregister(hotplug_slot);
 | 
			
		||||
				err("cannot create additional sysfs entries\n");
 | 
			
		||||
				goto error_info;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		list_add(&slot->slot_list, &ctrl->slot_list);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -183,6 +283,9 @@ static void cleanup_slots(struct controller *ctrl)
 | 
			
		|||
	list_for_each_safe(tmp, next, &ctrl->slot_list) {
 | 
			
		||||
		slot = list_entry(tmp, struct slot, slot_list);
 | 
			
		||||
		list_del(&slot->slot_list);
 | 
			
		||||
		if (EMI(ctrl->ctrlcap))
 | 
			
		||||
			sysfs_remove_file(&slot->hotplug_slot->kobj,
 | 
			
		||||
				&hotplug_slot_attr_lock.attr);
 | 
			
		||||
		pci_hp_deregister(slot->hotplug_slot);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,7 @@
 | 
			
		|||
#include <linux/timer.h>
 | 
			
		||||
#include <linux/pci.h>
 | 
			
		||||
#include <linux/interrupt.h>
 | 
			
		||||
#include <linux/time.h>
 | 
			
		||||
 | 
			
		||||
#include "../pci.h"
 | 
			
		||||
#include "pciehp.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -192,6 +193,7 @@ static inline int pciehp_writel(struct controller *ctrl, int reg, u32 value)
 | 
			
		|||
#define ATTN_LED_CTRL			0x00C0
 | 
			
		||||
#define PWR_LED_CTRL			0x0300
 | 
			
		||||
#define PWR_CTRL			0x0400
 | 
			
		||||
#define EMI_CTRL			0x0800
 | 
			
		||||
 | 
			
		||||
/* Attention indicator and Power indicator states */
 | 
			
		||||
#define LED_ON		0x01
 | 
			
		||||
| 
						 | 
				
			
			@ -202,6 +204,10 @@ static inline int pciehp_writel(struct controller *ctrl, int reg, u32 value)
 | 
			
		|||
#define POWER_ON	0
 | 
			
		||||
#define POWER_OFF	0x0400
 | 
			
		||||
 | 
			
		||||
/* EMI Status defines */
 | 
			
		||||
#define EMI_DISENGAGED	0
 | 
			
		||||
#define EMI_ENGAGED	1
 | 
			
		||||
 | 
			
		||||
/* Field definitions in Slot Status Register */
 | 
			
		||||
#define ATTN_BUTTN_PRESSED	0x0001
 | 
			
		||||
#define PWR_FAULT_DETECTED	0x0002
 | 
			
		||||
| 
						 | 
				
			
			@ -210,6 +216,8 @@ static inline int pciehp_writel(struct controller *ctrl, int reg, u32 value)
 | 
			
		|||
#define CMD_COMPLETED		0x0010
 | 
			
		||||
#define MRL_STATE		0x0020
 | 
			
		||||
#define PRSN_STATE		0x0040
 | 
			
		||||
#define EMI_STATE		0x0080
 | 
			
		||||
#define EMI_STATUS_BIT		7
 | 
			
		||||
 | 
			
		||||
static spinlock_t hpc_event_lock;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -474,6 +482,51 @@ static int hpc_query_power_fault(struct slot *slot)
 | 
			
		|||
	return pwr_fault;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int hpc_get_emi_status(struct slot *slot, u8 *status)
 | 
			
		||||
{
 | 
			
		||||
	struct controller *ctrl = slot->ctrl;
 | 
			
		||||
	u16 slot_status;
 | 
			
		||||
	int retval = 0;
 | 
			
		||||
 | 
			
		||||
	DBG_ENTER_ROUTINE
 | 
			
		||||
 | 
			
		||||
	retval = pciehp_readw(ctrl, SLOTSTATUS, &slot_status);
 | 
			
		||||
	if (retval) {
 | 
			
		||||
		err("%s : Cannot check EMI status\n", __FUNCTION__);
 | 
			
		||||
		return retval;
 | 
			
		||||
	}
 | 
			
		||||
	*status = (slot_status & EMI_STATE) >> EMI_STATUS_BIT;
 | 
			
		||||
 | 
			
		||||
	DBG_LEAVE_ROUTINE
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int hpc_toggle_emi(struct slot *slot)
 | 
			
		||||
{
 | 
			
		||||
	struct controller *ctrl = slot->ctrl;
 | 
			
		||||
	u16 slot_cmd = 0;
 | 
			
		||||
	u16 slot_ctrl;
 | 
			
		||||
	int rc = 0;
 | 
			
		||||
 | 
			
		||||
	DBG_ENTER_ROUTINE
 | 
			
		||||
 | 
			
		||||
	rc = pciehp_readw(ctrl, SLOTCTRL, &slot_ctrl);
 | 
			
		||||
	if (rc) {
 | 
			
		||||
		err("%s : hp_register_read_word SLOT_CTRL failed\n",
 | 
			
		||||
			__FUNCTION__);
 | 
			
		||||
		return rc;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	slot_cmd = (slot_ctrl | EMI_CTRL);
 | 
			
		||||
	if (!pciehp_poll_mode)
 | 
			
		||||
		slot_cmd = slot_cmd | HP_INTR_ENABLE;
 | 
			
		||||
 | 
			
		||||
	pcie_write_cmd(slot, slot_cmd);
 | 
			
		||||
	slot->last_emi_toggle = get_seconds();
 | 
			
		||||
	DBG_LEAVE_ROUTINE
 | 
			
		||||
	return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int hpc_set_attention_status(struct slot *slot, u8 value)
 | 
			
		||||
{
 | 
			
		||||
	struct controller *ctrl = slot->ctrl;
 | 
			
		||||
| 
						 | 
				
			
			@ -1009,6 +1062,8 @@ static struct hpc_ops pciehp_hpc_ops = {
 | 
			
		|||
	.get_attention_status		= hpc_get_attention_status,
 | 
			
		||||
	.get_latch_status		= hpc_get_latch_status,
 | 
			
		||||
	.get_adapter_status		= hpc_get_adapter_status,
 | 
			
		||||
	.get_emi_status			= hpc_get_emi_status,
 | 
			
		||||
	.toggle_emi			= hpc_toggle_emi,
 | 
			
		||||
 | 
			
		||||
	.get_max_bus_speed		= hpc_get_max_lnk_speed,
 | 
			
		||||
	.get_cur_bus_speed		= hpc_get_cur_lnk_speed,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue