2006-10-04 13:16:55 +04:00
/*
* File : htirq . c
* Purpose : Hypertransport Interrupt Capability
*
* Copyright ( C ) 2006 Linux Networx
* Copyright ( C ) Eric Biederman < ebiederman @ lnxi . com >
*/
# include <linux/irq.h>
# include <linux/pci.h>
# include <linux/spinlock.h>
# include <linux/slab.h>
# include <linux/gfp.h>
2006-10-04 13:17:01 +04:00
# include <linux/htirq.h>
2006-10-04 13:16:55 +04:00
/* Global ht irq lock.
*
* This is needed to serialize access to the data port in hypertransport
* irq capability .
*
* With multiple simultaneous hypertransport irq devices it might pay
* to make this more fine grained . But start with simple , stupid , and correct .
*/
static DEFINE_SPINLOCK ( ht_irq_lock ) ;
struct ht_irq_cfg {
struct pci_dev * dev ;
2006-11-09 04:44:57 +03:00
/* Update callback used to cope with buggy hardware */
ht_irq_update_t * update ;
2006-10-04 13:16:55 +04:00
unsigned pos ;
unsigned idx ;
2006-11-09 04:44:57 +03:00
struct ht_irq_msg msg ;
2006-10-04 13:16:55 +04:00
} ;
2006-11-09 04:44:57 +03:00
void write_ht_irq_msg ( unsigned int irq , struct ht_irq_msg * msg )
2006-10-04 13:16:55 +04:00
{
struct ht_irq_cfg * cfg = get_irq_data ( irq ) ;
unsigned long flags ;
spin_lock_irqsave ( & ht_irq_lock , flags ) ;
2006-11-09 04:44:57 +03:00
if ( cfg - > msg . address_lo ! = msg - > address_lo ) {
pci_write_config_byte ( cfg - > dev , cfg - > pos + 2 , cfg - > idx ) ;
pci_write_config_dword ( cfg - > dev , cfg - > pos + 4 , msg - > address_lo ) ;
}
if ( cfg - > msg . address_hi ! = msg - > address_hi ) {
pci_write_config_byte ( cfg - > dev , cfg - > pos + 2 , cfg - > idx + 1 ) ;
pci_write_config_dword ( cfg - > dev , cfg - > pos + 4 , msg - > address_hi ) ;
}
2006-11-09 04:44:57 +03:00
if ( cfg - > update )
cfg - > update ( cfg - > dev , irq , msg ) ;
2006-10-04 13:16:55 +04:00
spin_unlock_irqrestore ( & ht_irq_lock , flags ) ;
2006-11-09 04:44:57 +03:00
cfg - > msg = * msg ;
2006-10-04 13:16:55 +04:00
}
2006-11-09 04:44:57 +03:00
void fetch_ht_irq_msg ( unsigned int irq , struct ht_irq_msg * msg )
2006-10-04 13:16:55 +04:00
{
struct ht_irq_cfg * cfg = get_irq_data ( irq ) ;
2006-11-09 04:44:57 +03:00
* msg = cfg - > msg ;
2006-10-04 13:16:55 +04:00
}
void mask_ht_irq ( unsigned int irq )
{
struct ht_irq_cfg * cfg ;
2006-11-09 04:44:57 +03:00
struct ht_irq_msg msg ;
2006-10-04 13:16:55 +04:00
cfg = get_irq_data ( irq ) ;
2006-11-09 04:44:57 +03:00
msg = cfg - > msg ;
msg . address_lo | = 1 ;
write_ht_irq_msg ( irq , & msg ) ;
2006-10-04 13:16:55 +04:00
}
void unmask_ht_irq ( unsigned int irq )
{
struct ht_irq_cfg * cfg ;
2006-11-09 04:44:57 +03:00
struct ht_irq_msg msg ;
2006-10-04 13:16:55 +04:00
cfg = get_irq_data ( irq ) ;
2006-11-09 04:44:57 +03:00
msg = cfg - > msg ;
msg . address_lo & = ~ 1 ;
write_ht_irq_msg ( irq , & msg ) ;
2006-10-04 13:16:55 +04:00
}
/**
2006-11-09 04:44:57 +03:00
* __ht_create_irq - create an irq and attach it to a device .
2006-10-04 13:16:55 +04:00
* @ dev : The hypertransport device to find the irq capability on .
* @ idx : Which of the possible irqs to attach to .
2006-11-09 04:44:57 +03:00
* @ update : Function to be called when changing the htirq message
2006-10-04 13:16:55 +04:00
*
* The irq number of the new irq or a negative error value is returned .
*/
2006-11-09 04:44:57 +03:00
int __ht_create_irq ( struct pci_dev * dev , int idx , ht_irq_update_t * update )
2006-10-04 13:16:55 +04:00
{
struct ht_irq_cfg * cfg ;
unsigned long flags ;
u32 data ;
int max_irq ;
int pos ;
int irq ;
2006-11-22 10:26:19 +03:00
pos = pci_find_ht_capability ( dev , HT_CAPTYPE_IRQ ) ;
2006-10-04 13:16:55 +04:00
if ( ! pos )
return - EINVAL ;
/* Verify the idx I want to use is in range */
spin_lock_irqsave ( & ht_irq_lock , flags ) ;
pci_write_config_byte ( dev , pos + 2 , 1 ) ;
pci_read_config_dword ( dev , pos + 4 , & data ) ;
spin_unlock_irqrestore ( & ht_irq_lock , flags ) ;
max_irq = ( data > > 16 ) & 0xff ;
if ( idx > max_irq )
return - EINVAL ;
cfg = kmalloc ( sizeof ( * cfg ) , GFP_KERNEL ) ;
if ( ! cfg )
return - ENOMEM ;
cfg - > dev = dev ;
2006-11-09 04:44:57 +03:00
cfg - > update = update ;
2006-10-04 13:16:55 +04:00
cfg - > pos = pos ;
cfg - > idx = 0x10 + ( idx * 2 ) ;
2006-11-09 04:44:57 +03:00
/* Initialize msg to a value that will never match the first write. */
cfg - > msg . address_lo = 0xffffffff ;
cfg - > msg . address_hi = 0xffffffff ;
2006-10-04 13:16:55 +04:00
irq = create_irq ( ) ;
if ( irq < 0 ) {
kfree ( cfg ) ;
return - EBUSY ;
}
set_irq_data ( irq , cfg ) ;
if ( arch_setup_ht_irq ( irq , dev ) < 0 ) {
ht_destroy_irq ( irq ) ;
return - EBUSY ;
}
return irq ;
}
2006-11-09 04:44:57 +03:00
/**
* ht_create_irq - create an irq and attach it to a device .
* @ dev : The hypertransport device to find the irq capability on .
* @ idx : Which of the possible irqs to attach to .
*
* ht_create_irq needs to be called for all hypertransport devices
* that generate irqs .
*
* The irq number of the new irq or a negative error value is returned .
*/
int ht_create_irq ( struct pci_dev * dev , int idx )
{
return __ht_create_irq ( dev , idx , NULL ) ;
}
2006-10-04 13:16:55 +04:00
/**
* ht_destroy_irq - destroy an irq created with ht_create_irq
*
* This reverses ht_create_irq removing the specified irq from
* existence . The irq should be free before this happens .
*/
void ht_destroy_irq ( unsigned int irq )
{
struct ht_irq_cfg * cfg ;
cfg = get_irq_data ( irq ) ;
set_irq_chip ( irq , NULL ) ;
set_irq_data ( irq , NULL ) ;
destroy_irq ( irq ) ;
kfree ( cfg ) ;
}
2006-11-09 04:44:57 +03:00
EXPORT_SYMBOL ( __ht_create_irq ) ;
2006-10-04 13:16:55 +04:00
EXPORT_SYMBOL ( ht_create_irq ) ;
EXPORT_SYMBOL ( ht_destroy_irq ) ;