2005-04-17 02:20:36 +04:00
/*
* Transmeta ' s Efficeon AGPGART driver .
*
* Based upon a diff by Linus around November ' 02.
*
* Ported to the 2.6 kernel by Carlos Puchol < cpglinux @ puchol . com >
* and H . Peter Anvin < hpa @ transmeta . com > .
*/
/*
* NOTE - cpg - 040217 :
*
* - when compiled as a module , after loading the module ,
* it will refuse to unload , indicating it is in use ,
* when it is not .
* - no s3 ( suspend to ram ) testing .
* - tested on the efficeon integrated nothbridge for tens
* of iterations of starting x and glxgears .
* - tested with radeon 9000 and radeon mobility m9 cards
* - tested with c3 / c4 enabled ( with the mobility m9 card )
*/
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/init.h>
# include <linux/agp_backend.h>
# include <linux/gfp.h>
# include <linux/page-flags.h>
# include <linux/mm.h>
# include "agp.h"
/*
* The real differences to the generic AGP code is
* in the GART mappings - a two - level setup with the
* first level being an on - chip 64 - entry table .
*
* The page array is filled through the ATTPAGE register
* ( Aperture Translation Table Page Register ) at 0xB8 . Bits :
* 31 : 20 : physical page address
* 11 : 9 : Page Attribute Table Index ( PATI )
* must match the PAT index for the
* mapped pages ( the 2 nd level page table pages
* themselves should be just regular WB - cacheable ,
* so this is normally zero . )
* 8 : Present
* 7 : 6 : reserved , write as zero
* 5 : 0 : GATT directory index : which 1 st - level entry
*
* The Efficeon AGP spec requires pages to be WB - cacheable
* but to be explicitly CLFLUSH ' d after any changes .
*/
# define EFFICEON_ATTPAGE 0xb8
# define EFFICEON_L1_SIZE 64 /* Number of PDE pages */
# define EFFICEON_PATI (0 << 9)
# define EFFICEON_PRESENT (1 << 8)
static struct _efficeon_private {
unsigned long l1_table [ EFFICEON_L1_SIZE ] ;
} efficeon_private ;
static struct gatt_mask efficeon_generic_masks [ ] =
{
{ . mask = 0x00000001 , . type = 0 }
} ;
static struct aper_size_info_lvl2 efficeon_generic_sizes [ 4 ] =
{
{ 256 , 65536 , 0 } ,
{ 128 , 32768 , 32 } ,
{ 64 , 16384 , 48 } ,
{ 32 , 8192 , 56 }
} ;
/*
* Control interfaces are largely identical to
* the legacy Intel 440 BX . .
*/
static int efficeon_fetch_size ( void )
{
int i ;
u16 temp ;
struct aper_size_info_lvl2 * values ;
pci_read_config_word ( agp_bridge - > dev , INTEL_APSIZE , & temp ) ;
values = A_SIZE_LVL2 ( agp_bridge - > driver - > aperture_sizes ) ;
for ( i = 0 ; i < agp_bridge - > driver - > num_aperture_sizes ; i + + ) {
if ( temp = = values [ i ] . size_value ) {
agp_bridge - > previous_size =
agp_bridge - > current_size = ( void * ) ( values + i ) ;
agp_bridge - > aperture_size_idx = i ;
return values [ i ] . size ;
}
}
return 0 ;
}
static void efficeon_tlbflush ( struct agp_memory * mem )
{
printk ( KERN_DEBUG PFX " efficeon_tlbflush() \n " ) ;
pci_write_config_dword ( agp_bridge - > dev , INTEL_AGPCTRL , 0x2200 ) ;
pci_write_config_dword ( agp_bridge - > dev , INTEL_AGPCTRL , 0x2280 ) ;
}
static void efficeon_cleanup ( void )
{
u16 temp ;
struct aper_size_info_lvl2 * previous_size ;
printk ( KERN_DEBUG PFX " efficeon_cleanup() \n " ) ;
previous_size = A_SIZE_LVL2 ( agp_bridge - > previous_size ) ;
pci_read_config_word ( agp_bridge - > dev , INTEL_NBXCFG , & temp ) ;
pci_write_config_word ( agp_bridge - > dev , INTEL_NBXCFG , temp & ~ ( 1 < < 9 ) ) ;
pci_write_config_word ( agp_bridge - > dev , INTEL_APSIZE ,
previous_size - > size_value ) ;
}
static int efficeon_configure ( void )
{
u32 temp ;
u16 temp2 ;
struct aper_size_info_lvl2 * current_size ;
printk ( KERN_DEBUG PFX " efficeon_configure() \n " ) ;
current_size = A_SIZE_LVL2 ( agp_bridge - > current_size ) ;
/* aperture size */
pci_write_config_word ( agp_bridge - > dev , INTEL_APSIZE ,
current_size - > size_value ) ;
/* address to map to */
pci_read_config_dword ( agp_bridge - > dev , AGP_APBASE , & temp ) ;
agp_bridge - > gart_bus_addr = ( temp & PCI_BASE_ADDRESS_MEM_MASK ) ;
/* agpctrl */
pci_write_config_dword ( agp_bridge - > dev , INTEL_AGPCTRL , 0x2280 ) ;
/* paccfg/nbxcfg */
pci_read_config_word ( agp_bridge - > dev , INTEL_NBXCFG , & temp2 ) ;
pci_write_config_word ( agp_bridge - > dev , INTEL_NBXCFG ,
( temp2 & ~ ( 1 < < 10 ) ) | ( 1 < < 9 ) | ( 1 < < 11 ) ) ;
/* clear any possible error conditions */
pci_write_config_byte ( agp_bridge - > dev , INTEL_ERRSTS + 1 , 7 ) ;
return 0 ;
}
static int efficeon_free_gatt_table ( struct agp_bridge_data * bridge )
{
int index , freed = 0 ;
for ( index = 0 ; index < EFFICEON_L1_SIZE ; index + + ) {
unsigned long page = efficeon_private . l1_table [ index ] ;
if ( page ) {
efficeon_private . l1_table [ index ] = 0 ;
ClearPageReserved ( virt_to_page ( ( char * ) page ) ) ;
free_page ( page ) ;
freed + + ;
}
printk ( KERN_DEBUG PFX " efficeon_free_gatt_table(%p, %02x, %08x) \n " ,
agp_bridge - > dev , EFFICEON_ATTPAGE , index ) ;
pci_write_config_dword ( agp_bridge - > dev ,
EFFICEON_ATTPAGE , index ) ;
}
printk ( KERN_DEBUG PFX " efficeon_free_gatt_table() freed %d pages \n " , freed ) ;
return 0 ;
}
/*
* Since we don ' t need contigious memory we just try
* to get the gatt table once
*/
# define GET_PAGE_DIR_OFF(addr) (addr >> 22)
# define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
GET_PAGE_DIR_OFF ( agp_bridge - > gart_bus_addr ) )
# define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
# undef GET_GATT
# define GET_GATT(addr) (efficeon_private.gatt_pages[\
GET_PAGE_DIR_IDX ( addr ) ] - > remapped )
static int efficeon_create_gatt_table ( struct agp_bridge_data * bridge )
{
int index ;
const int pati = EFFICEON_PATI ;
const int present = EFFICEON_PRESENT ;
const int clflush_chunk = ( ( cpuid_ebx ( 1 ) > > 8 ) & 0xff ) < < 3 ;
int num_entries , l1_pages ;
num_entries = A_SIZE_LVL2 ( agp_bridge - > current_size ) - > num_entries ;
printk ( KERN_DEBUG PFX " efficeon_create_gatt_table(%d) \n " , num_entries ) ;
/* There are 2^10 PTE pages per PDE page */
BUG_ON ( num_entries & 0x3ff ) ;
l1_pages = num_entries > > 10 ;
for ( index = 0 ; index < l1_pages ; index + + ) {
int offset ;
unsigned long page ;
unsigned long value ;
page = efficeon_private . l1_table [ index ] ;
BUG_ON ( page ) ;
page = get_zeroed_page ( GFP_KERNEL ) ;
if ( ! page ) {
efficeon_free_gatt_table ( agp_bridge ) ;
return - ENOMEM ;
}
SetPageReserved ( virt_to_page ( ( char * ) page ) ) ;
for ( offset = 0 ; offset < PAGE_SIZE ; offset + = clflush_chunk )
asm volatile ( " clflush %0 " : : " m " ( * ( char * ) ( page + offset ) ) ) ;
efficeon_private . l1_table [ index ] = page ;
value = __pa ( page ) | pati | present | index ;
pci_write_config_dword ( agp_bridge - > dev ,
EFFICEON_ATTPAGE , value ) ;
}
return 0 ;
}
static int efficeon_insert_memory ( struct agp_memory * mem , off_t pg_start , int type )
{
int i , count = mem - > page_count , num_entries ;
unsigned int * page , * last_page ;
const int clflush_chunk = ( ( cpuid_ebx ( 1 ) > > 8 ) & 0xff ) < < 3 ;
const unsigned long clflush_mask = ~ ( clflush_chunk - 1 ) ;
printk ( KERN_DEBUG PFX " efficeon_insert_memory(%lx, %d) \n " , pg_start , count ) ;
num_entries = A_SIZE_LVL2 ( agp_bridge - > current_size ) - > num_entries ;
if ( ( pg_start + mem - > page_count ) > num_entries )
return - EINVAL ;
if ( type ! = 0 | | mem - > type ! = 0 )
return - EINVAL ;
if ( mem - > is_flushed = = FALSE ) {
global_cache_flush ( ) ;
mem - > is_flushed = TRUE ;
}
last_page = NULL ;
for ( i = 0 ; i < count ; i + + ) {
int index = pg_start + i ;
unsigned long insert = mem - > memory [ i ] ;
page = ( unsigned int * ) efficeon_private . l1_table [ index > > 10 ] ;
if ( ! page )
continue ;
page + = ( index & 0x3ff ) ;
* page = insert ;
/* clflush is slow, so don't clflush until we have to */
if ( last_page & &
( ( unsigned long ) page ^ ( unsigned long ) last_page ) & clflush_mask )
asm volatile ( " clflush %0 " : : " m " ( * last_page ) ) ;
last_page = page ;
}
if ( last_page )
asm volatile ( " clflush %0 " : : " m " ( * last_page ) ) ;
agp_bridge - > driver - > tlb_flush ( mem ) ;
return 0 ;
}
static int efficeon_remove_memory ( struct agp_memory * mem , off_t pg_start , int type )
{
int i , count = mem - > page_count , num_entries ;
printk ( KERN_DEBUG PFX " efficeon_remove_memory(%lx, %d) \n " , pg_start , count ) ;
num_entries = A_SIZE_LVL2 ( agp_bridge - > current_size ) - > num_entries ;
if ( ( pg_start + mem - > page_count ) > num_entries )
return - EINVAL ;
if ( type ! = 0 | | mem - > type ! = 0 )
return - EINVAL ;
for ( i = 0 ; i < count ; i + + ) {
int index = pg_start + i ;
unsigned int * page = ( unsigned int * ) efficeon_private . l1_table [ index > > 10 ] ;
if ( ! page )
continue ;
page + = ( index & 0x3ff ) ;
* page = 0 ;
}
agp_bridge - > driver - > tlb_flush ( mem ) ;
return 0 ;
}
struct agp_bridge_driver efficeon_driver = {
. owner = THIS_MODULE ,
. aperture_sizes = efficeon_generic_sizes ,
. size_type = LVL2_APER_SIZE ,
. num_aperture_sizes = 4 ,
. configure = efficeon_configure ,
. fetch_size = efficeon_fetch_size ,
. cleanup = efficeon_cleanup ,
. tlb_flush = efficeon_tlbflush ,
. mask_memory = agp_generic_mask_memory ,
. masks = efficeon_generic_masks ,
. agp_enable = agp_generic_enable ,
. cache_flush = global_cache_flush ,
// Efficeon-specific GATT table setup / populate / teardown
. create_gatt_table = efficeon_create_gatt_table ,
. free_gatt_table = efficeon_free_gatt_table ,
. insert_memory = efficeon_insert_memory ,
. remove_memory = efficeon_remove_memory ,
. cant_use_aperture = 0 , // 1 might be faster?
// Generic
. alloc_by_type = agp_generic_alloc_by_type ,
. free_by_type = agp_generic_free_by_type ,
. agp_alloc_page = agp_generic_alloc_page ,
. agp_destroy_page = agp_generic_destroy_page ,
} ;
static int agp_efficeon_resume ( struct pci_dev * pdev )
{
printk ( KERN_DEBUG PFX " agp_efficeon_resume() \n " ) ;
return efficeon_configure ( ) ;
}
static int __devinit agp_efficeon_probe ( struct pci_dev * pdev ,
const struct pci_device_id * ent )
{
struct agp_bridge_data * bridge ;
u8 cap_ptr ;
struct resource * r ;
cap_ptr = pci_find_capability ( pdev , PCI_CAP_ID_AGP ) ;
if ( ! cap_ptr )
return - ENODEV ;
/* Probe for Efficeon controller */
if ( pdev - > device ! = PCI_DEVICE_ID_EFFICEON ) {
printk ( KERN_ERR PFX " Unsupported Efficeon chipset (device id: %04x) \n " ,
pdev - > device ) ;
return - ENODEV ;
}
printk ( KERN_INFO PFX " Detected Transmeta Efficeon TM8000 series chipset \n " ) ;
bridge = agp_alloc_bridge ( ) ;
if ( ! bridge )
return - ENOMEM ;
bridge - > driver = & efficeon_driver ;
bridge - > dev = pdev ;
bridge - > capndx = cap_ptr ;
/*
* The following fixes the case where the BIOS has " forgotten " to
* provide an address range for the GART .
* 20030610 - hamish @ zot . org
*/
r = & pdev - > resource [ 0 ] ;
if ( ! r - > start & & r - > end ) {
if ( pci_assign_resource ( pdev , 0 ) ) {
printk ( KERN_ERR PFX " could not assign resource 0 \n " ) ;
return - ENODEV ;
}
}
/*
* If the device has not been properly setup , the following will catch
* the problem and should stop the system from crashing .
* 20030610 - hamish @ zot . org
*/
if ( pci_enable_device ( pdev ) ) {
printk ( KERN_ERR PFX " Unable to Enable PCI device \n " ) ;
return - ENODEV ;
}
/* Fill in the mode register */
if ( cap_ptr ) {
pci_read_config_dword ( pdev ,
bridge - > capndx + PCI_AGP_STATUS ,
& bridge - > mode ) ;
}
pci_set_drvdata ( pdev , bridge ) ;
return agp_add_bridge ( bridge ) ;
}
static void __devexit agp_efficeon_remove ( struct pci_dev * pdev )
{
struct agp_bridge_data * bridge = pci_get_drvdata ( pdev ) ;
agp_remove_bridge ( bridge ) ;
agp_put_bridge ( bridge ) ;
}
2005-04-17 02:25:24 +04:00
static int agp_efficeon_suspend ( struct pci_dev * dev , pm_message_t state )
2005-04-17 02:20:36 +04:00
{
return 0 ;
}
static struct pci_device_id agp_efficeon_pci_table [ ] = {
{
. class = ( PCI_CLASS_BRIDGE_HOST < < 8 ) ,
. class_mask = ~ 0 ,
. vendor = PCI_VENDOR_ID_TRANSMETA ,
. device = PCI_ANY_ID ,
. subvendor = PCI_ANY_ID ,
. subdevice = PCI_ANY_ID ,
} ,
{ }
} ;
MODULE_DEVICE_TABLE ( pci , agp_efficeon_pci_table ) ;
static struct pci_driver agp_efficeon_pci_driver = {
. name = " agpgart-efficeon " ,
. id_table = agp_efficeon_pci_table ,
. probe = agp_efficeon_probe ,
. remove = agp_efficeon_remove ,
. suspend = agp_efficeon_suspend ,
. resume = agp_efficeon_resume ,
} ;
static int __init agp_efficeon_init ( void )
{
static int agp_initialised = 0 ;
if ( agp_off )
return - EINVAL ;
if ( agp_initialised = = 1 )
return 0 ;
agp_initialised = 1 ;
return pci_register_driver ( & agp_efficeon_pci_driver ) ;
}
static void __exit agp_efficeon_cleanup ( void )
{
pci_unregister_driver ( & agp_efficeon_pci_driver ) ;
}
module_init ( agp_efficeon_init ) ;
module_exit ( agp_efficeon_cleanup ) ;
MODULE_AUTHOR ( " Carlos Puchol <cpglinux@puchol.com> " ) ;
MODULE_LICENSE ( " GPL and additional rights " ) ;