2005-04-17 02:20:36 +04:00
/*
* Linux driver attachment glue for PCI based U320 controllers .
*
* Copyright ( c ) 2000 - 2001 Adaptec Inc .
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice , this list of conditions , and the following disclaimer ,
* without modification .
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* substantially similar to the " NO WARRANTY " disclaimer below
* ( " Disclaimer " ) and any redistribution must be conditioned upon
* including a substantially similar Disclaimer requirement for further
* binary redistribution .
* 3. Neither the names of the above - listed copyright holders nor the names
* of any contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* Alternatively , this software may be distributed under the terms of the
* GNU General Public License ( " GPL " ) version 2 as published by the Free
* Software Foundation .
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* " AS IS " AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT
* LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES .
*
* $ Id : //depot/aic7xxx/linux/drivers/scsi/aic7xxx/aic79xx_osm_pci.c#25 $
*/
# include "aic79xx_osm.h"
# include "aic79xx_inline.h"
# include "aic79xx_pci.h"
/* Define the macro locally since it's different for different class of chips.
*/
# define ID(x) \
ID2C ( x ) , \
ID2C ( IDIROC ( x ) )
2008-04-25 06:36:01 +04:00
static const struct pci_device_id ahd_linux_pci_id_table [ ] = {
2005-04-17 02:20:36 +04:00
/* aic7901 based controllers */
ID ( ID_AHA_29320A ) ,
ID ( ID_AHA_29320ALP ) ,
2006-12-05 22:37:30 +03:00
ID ( ID_AHA_29320LPE ) ,
2005-04-17 02:20:36 +04:00
/* aic7902 based controllers */
ID ( ID_AHA_29320 ) ,
ID ( ID_AHA_29320B ) ,
ID ( ID_AHA_29320LP ) ,
ID ( ID_AHA_39320 ) ,
ID ( ID_AHA_39320_B ) ,
ID ( ID_AHA_39320A ) ,
ID ( ID_AHA_39320D ) ,
ID ( ID_AHA_39320D_HP ) ,
ID ( ID_AHA_39320D_B ) ,
ID ( ID_AHA_39320D_B_HP ) ,
/* Generic chip probes for devices we don't know exactly. */
ID16 ( ID_AIC7901 & ID_9005_GENERIC_MASK ) ,
ID ( ID_AIC7901A & ID_DEV_VENDOR_MASK ) ,
ID16 ( ID_AIC7902 & ID_9005_GENERIC_MASK ) ,
{ 0 }
} ;
MODULE_DEVICE_TABLE ( pci , ahd_linux_pci_id_table ) ;
2007-10-19 12:32:21 +04:00
# ifdef CONFIG_PM
static int
ahd_linux_pci_dev_suspend ( struct pci_dev * pdev , pm_message_t mesg )
{
struct ahd_softc * ahd = pci_get_drvdata ( pdev ) ;
int rc ;
if ( ( rc = ahd_suspend ( ahd ) ) )
return rc ;
ahd_pci_suspend ( ahd ) ;
pci_save_state ( pdev ) ;
pci_disable_device ( pdev ) ;
2008-02-23 21:13:25 +03:00
if ( mesg . event & PM_EVENT_SLEEP )
2007-10-19 12:32:21 +04:00
pci_set_power_state ( pdev , PCI_D3hot ) ;
return rc ;
}
static int
ahd_linux_pci_dev_resume ( struct pci_dev * pdev )
{
struct ahd_softc * ahd = pci_get_drvdata ( pdev ) ;
int rc ;
pci_set_power_state ( pdev , PCI_D0 ) ;
pci_restore_state ( pdev ) ;
if ( ( rc = pci_enable_device ( pdev ) ) ) {
dev_printk ( KERN_ERR , & pdev - > dev ,
" failed to enable device after resume (%d) \n " , rc ) ;
return rc ;
}
pci_set_master ( pdev ) ;
ahd_pci_resume ( ahd ) ;
ahd_resume ( ahd ) ;
return rc ;
}
2008-01-26 18:08:18 +03:00
# endif
2007-10-19 12:32:21 +04:00
2005-04-17 02:20:36 +04:00
static void
ahd_linux_pci_dev_remove ( struct pci_dev * pdev )
{
2005-08-15 15:28:46 +04:00
struct ahd_softc * ahd = pci_get_drvdata ( pdev ) ;
u_long s ;
2005-04-17 02:20:36 +04:00
2005-10-03 00:22:35 +04:00
if ( ahd - > platform_data & & ahd - > platform_data - > host )
scsi_remove_host ( ahd - > platform_data - > host ) ;
2005-08-15 15:28:46 +04:00
ahd_lock ( ahd , & s ) ;
ahd_intr_enable ( ahd , FALSE ) ;
ahd_unlock ( ahd , & s ) ;
ahd_free ( ahd ) ;
}
static void
ahd_linux_pci_inherit_flags ( struct ahd_softc * ahd )
{
struct pci_dev * pdev = ahd - > dev_softc , * master_pdev ;
unsigned int master_devfn = PCI_DEVFN ( PCI_SLOT ( pdev - > devfn ) , 0 ) ;
master_pdev = pci_get_slot ( pdev - > bus , master_devfn ) ;
if ( master_pdev ) {
struct ahd_softc * master = pci_get_drvdata ( master_pdev ) ;
if ( master ) {
ahd - > flags & = ~ AHD_BIOS_ENABLED ;
ahd - > flags | = master - > flags & AHD_BIOS_ENABLED ;
} else
printk ( KERN_ERR " aic79xx: no multichannel peer found! \n " ) ;
pci_dev_put ( master_pdev ) ;
}
2005-04-17 02:20:36 +04:00
}
static int
ahd_linux_pci_dev_probe ( struct pci_dev * pdev , const struct pci_device_id * ent )
{
char buf [ 80 ] ;
struct ahd_softc * ahd ;
ahd_dev_softc_t pci ;
2008-04-25 06:36:01 +04:00
const struct ahd_pci_identity * entry ;
2005-04-17 02:20:36 +04:00
char * name ;
int error ;
2007-02-07 11:47:44 +03:00
struct device * dev = & pdev - > dev ;
2005-04-17 02:20:36 +04:00
pci = pdev ;
entry = ahd_find_pci_device ( pci ) ;
if ( entry = = NULL )
return ( - ENODEV ) ;
/*
* Allocate a softc for this card and
* set it up for attachment by our
* common detect routine .
*/
sprintf ( buf , " ahd_pci:%d:%d:%d " ,
ahd_get_pci_bus ( pci ) ,
ahd_get_pci_slot ( pci ) ,
ahd_get_pci_function ( pci ) ) ;
2010-07-14 14:12:57 +04:00
name = kmalloc ( strlen ( buf ) + 1 , GFP_ATOMIC ) ;
2005-04-17 02:20:36 +04:00
if ( name = = NULL )
return ( - ENOMEM ) ;
strcpy ( name , buf ) ;
ahd = ahd_alloc ( NULL , name ) ;
if ( ahd = = NULL )
return ( - ENOMEM ) ;
if ( pci_enable_device ( pdev ) ) {
ahd_free ( ahd ) ;
return ( - ENODEV ) ;
}
pci_set_master ( pdev ) ;
if ( sizeof ( dma_addr_t ) > 4 ) {
2007-02-07 11:47:44 +03:00
const u64 required_mask = dma_get_required_mask ( dev ) ;
2005-04-17 02:20:36 +04:00
2009-04-07 06:01:15 +04:00
if ( required_mask > DMA_BIT_MASK ( 39 ) & &
2009-04-07 06:01:13 +04:00
dma_set_mask ( dev , DMA_BIT_MASK ( 64 ) ) = = 0 )
2005-04-17 02:20:36 +04:00
ahd - > flags | = AHD_64BIT_ADDRESSING ;
2009-04-07 06:01:15 +04:00
else if ( required_mask > DMA_BIT_MASK ( 32 ) & &
2009-04-07 06:01:15 +04:00
dma_set_mask ( dev , DMA_BIT_MASK ( 39 ) ) = = 0 )
2005-04-17 02:20:36 +04:00
ahd - > flags | = AHD_39BIT_ADDRESSING ;
2007-02-07 11:47:44 +03:00
else
2009-04-07 06:01:15 +04:00
dma_set_mask ( dev , DMA_BIT_MASK ( 32 ) ) ;
2005-04-17 02:20:36 +04:00
} else {
2009-04-07 06:01:15 +04:00
dma_set_mask ( dev , DMA_BIT_MASK ( 32 ) ) ;
2005-04-17 02:20:36 +04:00
}
ahd - > dev_softc = pci ;
error = ahd_pci_config ( ahd , entry ) ;
if ( error ! = 0 ) {
ahd_free ( ahd ) ;
return ( - error ) ;
}
2005-08-15 15:28:46 +04:00
/*
* Second Function PCI devices need to inherit some
* * settings from function 0.
*/
if ( ( ahd - > features & AHD_MULTI_FUNC ) & & PCI_FUNC ( pdev - > devfn ) ! = 0 )
ahd_linux_pci_inherit_flags ( ahd ) ;
2005-04-17 02:20:36 +04:00
pci_set_drvdata ( pdev , ahd ) ;
2005-08-15 15:28:46 +04:00
ahd_linux_register_host ( ahd , & aic79xx_driver_template ) ;
2005-04-17 02:20:36 +04:00
return ( 0 ) ;
}
2008-01-26 18:08:18 +03:00
static struct pci_driver aic79xx_pci_driver = {
. name = " aic79xx " ,
. probe = ahd_linux_pci_dev_probe ,
# ifdef CONFIG_PM
. suspend = ahd_linux_pci_dev_suspend ,
. resume = ahd_linux_pci_dev_resume ,
# endif
. remove = ahd_linux_pci_dev_remove ,
. id_table = ahd_linux_pci_id_table
} ;
2005-04-17 02:20:36 +04:00
int
ahd_linux_pci_init ( void )
{
2006-09-26 03:58:58 +04:00
return pci_register_driver ( & aic79xx_pci_driver ) ;
2005-04-17 02:20:36 +04:00
}
void
ahd_linux_pci_exit ( void )
{
pci_unregister_driver ( & aic79xx_pci_driver ) ;
}
static int
2008-04-18 23:39:03 +04:00
ahd_linux_pci_reserve_io_regions ( struct ahd_softc * ahd , resource_size_t * base ,
resource_size_t * base2 )
2005-04-17 02:20:36 +04:00
{
* base = pci_resource_start ( ahd - > dev_softc , 0 ) ;
/*
* This is really the 3 rd bar and should be at index 2 ,
* but the Linux PCI code doesn ' t know how to " count " 64 bit
* bars .
*/
* base2 = pci_resource_start ( ahd - > dev_softc , 3 ) ;
if ( * base = = 0 | | * base2 = = 0 )
return ( ENOMEM ) ;
2006-01-24 12:43:26 +03:00
if ( ! request_region ( * base , 256 , " aic79xx " ) )
2005-04-17 02:20:36 +04:00
return ( ENOMEM ) ;
2006-01-24 12:43:26 +03:00
if ( ! request_region ( * base2 , 256 , " aic79xx " ) ) {
release_region ( * base , 256 ) ;
2005-04-17 02:20:36 +04:00
return ( ENOMEM ) ;
}
return ( 0 ) ;
}
static int
ahd_linux_pci_reserve_mem_region ( struct ahd_softc * ahd ,
2008-04-18 23:39:03 +04:00
resource_size_t * bus_addr ,
2005-04-17 02:20:36 +04:00
uint8_t __iomem * * maddr )
{
2008-04-18 23:39:03 +04:00
resource_size_t start ;
resource_size_t base_page ;
2005-04-17 02:20:36 +04:00
u_long base_offset ;
2006-01-24 12:43:26 +03:00
int error = 0 ;
2005-04-17 02:20:36 +04:00
if ( aic79xx_allow_memio = = 0 )
return ( ENOMEM ) ;
if ( ( ahd - > bugs & AHD_PCIX_MMAPIO_BUG ) ! = 0 )
return ( ENOMEM ) ;
start = pci_resource_start ( ahd - > dev_softc , 1 ) ;
base_page = start & PAGE_MASK ;
base_offset = start - base_page ;
if ( start ! = 0 ) {
* bus_addr = start ;
2006-01-24 12:43:26 +03:00
if ( ! request_mem_region ( start , 0x1000 , " aic79xx " ) )
2005-04-17 02:20:36 +04:00
error = ENOMEM ;
2006-01-24 12:43:26 +03:00
if ( ! error ) {
* maddr = ioremap_nocache ( base_page , base_offset + 512 ) ;
2005-04-17 02:20:36 +04:00
if ( * maddr = = NULL ) {
error = ENOMEM ;
release_mem_region ( start , 0x1000 ) ;
} else
* maddr + = base_offset ;
}
} else
error = ENOMEM ;
return ( error ) ;
}
int
ahd_pci_map_registers ( struct ahd_softc * ahd )
{
uint32_t command ;
2008-04-18 23:39:03 +04:00
resource_size_t base ;
2005-04-17 02:20:36 +04:00
uint8_t __iomem * maddr ;
int error ;
/*
* If its allowed , we prefer memory mapped access .
*/
command = ahd_pci_read_config ( ahd - > dev_softc , PCIR_COMMAND , 4 ) ;
command & = ~ ( PCIM_CMD_PORTEN | PCIM_CMD_MEMEN ) ;
base = 0 ;
maddr = NULL ;
error = ahd_linux_pci_reserve_mem_region ( ahd , & base , & maddr ) ;
if ( error = = 0 ) {
ahd - > platform_data - > mem_busaddr = base ;
ahd - > tags [ 0 ] = BUS_SPACE_MEMIO ;
ahd - > bshs [ 0 ] . maddr = maddr ;
ahd - > tags [ 1 ] = BUS_SPACE_MEMIO ;
ahd - > bshs [ 1 ] . maddr = maddr + 0x100 ;
ahd_pci_write_config ( ahd - > dev_softc , PCIR_COMMAND ,
command | PCIM_CMD_MEMEN , 4 ) ;
if ( ahd_pci_test_register_access ( ahd ) ! = 0 ) {
2010-07-14 14:12:57 +04:00
printk ( " aic79xx: PCI Device %d:%d:%d "
2005-04-17 02:20:36 +04:00
" failed memory mapped test. Using PIO. \n " ,
ahd_get_pci_bus ( ahd - > dev_softc ) ,
ahd_get_pci_slot ( ahd - > dev_softc ) ,
ahd_get_pci_function ( ahd - > dev_softc ) ) ;
iounmap ( maddr ) ;
release_mem_region ( ahd - > platform_data - > mem_busaddr ,
0x1000 ) ;
ahd - > bshs [ 0 ] . maddr = NULL ;
maddr = NULL ;
} else
command | = PCIM_CMD_MEMEN ;
} else if ( bootverbose ) {
2010-07-14 14:12:57 +04:00
printk ( " aic79xx: PCI%d:%d:%d MEM region 0x%llx "
2005-04-17 02:20:36 +04:00
" unavailable. Cannot memory map device. \n " ,
ahd_get_pci_bus ( ahd - > dev_softc ) ,
ahd_get_pci_slot ( ahd - > dev_softc ) ,
ahd_get_pci_function ( ahd - > dev_softc ) ,
2008-04-18 23:39:03 +04:00
( unsigned long long ) base ) ;
2005-04-17 02:20:36 +04:00
}
if ( maddr = = NULL ) {
2008-04-18 23:39:03 +04:00
resource_size_t base2 ;
2005-04-17 02:20:36 +04:00
error = ahd_linux_pci_reserve_io_regions ( ahd , & base , & base2 ) ;
if ( error = = 0 ) {
ahd - > tags [ 0 ] = BUS_SPACE_PIO ;
ahd - > tags [ 1 ] = BUS_SPACE_PIO ;
2008-04-18 23:39:03 +04:00
ahd - > bshs [ 0 ] . ioport = ( u_long ) base ;
ahd - > bshs [ 1 ] . ioport = ( u_long ) base2 ;
2005-04-17 02:20:36 +04:00
command | = PCIM_CMD_PORTEN ;
} else {
2010-07-14 14:12:57 +04:00
printk ( " aic79xx: PCI%d:%d:%d IO regions 0x%llx and "
2008-04-18 23:39:03 +04:00
" 0x%llx unavailable. Cannot map device. \n " ,
2005-04-17 02:20:36 +04:00
ahd_get_pci_bus ( ahd - > dev_softc ) ,
ahd_get_pci_slot ( ahd - > dev_softc ) ,
ahd_get_pci_function ( ahd - > dev_softc ) ,
2008-04-18 23:39:03 +04:00
( unsigned long long ) base ,
( unsigned long long ) base2 ) ;
2005-04-17 02:20:36 +04:00
}
}
ahd_pci_write_config ( ahd - > dev_softc , PCIR_COMMAND , command , 4 ) ;
return ( error ) ;
}
int
ahd_pci_map_int ( struct ahd_softc * ahd )
{
int error ;
error = request_irq ( ahd - > dev_softc - > irq , ahd_linux_isr ,
2006-07-02 06:29:42 +04:00
IRQF_SHARED , " aic79xx " , ahd ) ;
2006-01-24 12:43:26 +03:00
if ( ! error )
2005-04-17 02:20:36 +04:00
ahd - > platform_data - > irq = ahd - > dev_softc - > irq ;
return ( - error ) ;
}
void
ahd_power_state_change ( struct ahd_softc * ahd , ahd_power_state new_state )
{
pci_set_power_state ( ahd - > dev_softc , new_state ) ;
}