![Oliver Neukum](/assets/img/avatar_default.png)
For some vendor/id pairs the kernel will autoload both the sis-agp and the amd64_agp modules as the sis-agp module will load for all sis devices. This collision causes the bug reported in: http://bugzilla.novell.com/show_bug.cgi?id=248665 As currently sis_probe does its own matching, requesting the whole range gains nothing. The clean fix seems to me to leave the matching to the core and advertise only the devices actually supported. This patch does so. Signed-off-by: Oliver Neukum <oneukum@suse.de> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Dave Jones <davej@redhat.com>
418 lines
10 KiB
C
418 lines
10 KiB
C
/*
|
|
* SiS AGPGART routines.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/init.h>
|
|
#include <linux/agp_backend.h>
|
|
#include <linux/delay.h>
|
|
#include "agp.h"
|
|
|
|
#define SIS_ATTBASE 0x90
|
|
#define SIS_APSIZE 0x94
|
|
#define SIS_TLBCNTRL 0x97
|
|
#define SIS_TLBFLUSH 0x98
|
|
|
|
static int __devinitdata agp_sis_force_delay = 0;
|
|
static int __devinitdata agp_sis_agp_spec = -1;
|
|
|
|
static int sis_fetch_size(void)
|
|
{
|
|
u8 temp_size;
|
|
int i;
|
|
struct aper_size_info_8 *values;
|
|
|
|
pci_read_config_byte(agp_bridge->dev, SIS_APSIZE, &temp_size);
|
|
values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
|
|
for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
|
|
if ((temp_size == values[i].size_value) ||
|
|
((temp_size & ~(0x03)) ==
|
|
(values[i].size_value & ~(0x03)))) {
|
|
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 sis_tlbflush(struct agp_memory *mem)
|
|
{
|
|
pci_write_config_byte(agp_bridge->dev, SIS_TLBFLUSH, 0x02);
|
|
}
|
|
|
|
static int sis_configure(void)
|
|
{
|
|
u32 temp;
|
|
struct aper_size_info_8 *current_size;
|
|
|
|
current_size = A_SIZE_8(agp_bridge->current_size);
|
|
pci_write_config_byte(agp_bridge->dev, SIS_TLBCNTRL, 0x05);
|
|
pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
|
|
agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
|
|
pci_write_config_dword(agp_bridge->dev, SIS_ATTBASE,
|
|
agp_bridge->gatt_bus_addr);
|
|
pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
|
|
current_size->size_value);
|
|
return 0;
|
|
}
|
|
|
|
static void sis_cleanup(void)
|
|
{
|
|
struct aper_size_info_8 *previous_size;
|
|
|
|
previous_size = A_SIZE_8(agp_bridge->previous_size);
|
|
pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
|
|
(previous_size->size_value & ~(0x03)));
|
|
}
|
|
|
|
static void sis_delayed_enable(struct agp_bridge_data *bridge, u32 mode)
|
|
{
|
|
struct pci_dev *device = NULL;
|
|
u32 command;
|
|
int rate;
|
|
|
|
printk(KERN_INFO PFX "Found an AGP %d.%d compliant device at %s.\n",
|
|
agp_bridge->major_version,
|
|
agp_bridge->minor_version,
|
|
pci_name(agp_bridge->dev));
|
|
|
|
pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx + PCI_AGP_STATUS, &command);
|
|
command = agp_collect_device_status(bridge, mode, command);
|
|
command |= AGPSTAT_AGP_ENABLE;
|
|
rate = (command & 0x7) << 2;
|
|
|
|
for_each_pci_dev(device) {
|
|
u8 agp = pci_find_capability(device, PCI_CAP_ID_AGP);
|
|
if (!agp)
|
|
continue;
|
|
|
|
printk(KERN_INFO PFX "Putting AGP V3 device at %s into %dx mode\n",
|
|
pci_name(device), rate);
|
|
|
|
pci_write_config_dword(device, agp + PCI_AGP_COMMAND, command);
|
|
|
|
/*
|
|
* Weird: on some sis chipsets any rate change in the target
|
|
* command register triggers a 5ms screwup during which the master
|
|
* cannot be configured
|
|
*/
|
|
if (device->device == bridge->dev->device) {
|
|
printk(KERN_INFO PFX "SiS delay workaround: giving bridge time to recover.\n");
|
|
msleep(10);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct aper_size_info_8 sis_generic_sizes[7] =
|
|
{
|
|
{256, 65536, 6, 99},
|
|
{128, 32768, 5, 83},
|
|
{64, 16384, 4, 67},
|
|
{32, 8192, 3, 51},
|
|
{16, 4096, 2, 35},
|
|
{8, 2048, 1, 19},
|
|
{4, 1024, 0, 3}
|
|
};
|
|
|
|
static struct agp_bridge_driver sis_driver = {
|
|
.owner = THIS_MODULE,
|
|
.aperture_sizes = sis_generic_sizes,
|
|
.size_type = U8_APER_SIZE,
|
|
.num_aperture_sizes = 7,
|
|
.configure = sis_configure,
|
|
.fetch_size = sis_fetch_size,
|
|
.cleanup = sis_cleanup,
|
|
.tlb_flush = sis_tlbflush,
|
|
.mask_memory = agp_generic_mask_memory,
|
|
.masks = NULL,
|
|
.agp_enable = agp_generic_enable,
|
|
.cache_flush = global_cache_flush,
|
|
.create_gatt_table = agp_generic_create_gatt_table,
|
|
.free_gatt_table = agp_generic_free_gatt_table,
|
|
.insert_memory = agp_generic_insert_memory,
|
|
.remove_memory = agp_generic_remove_memory,
|
|
.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,
|
|
.agp_type_to_mask_type = agp_generic_type_to_mask_type,
|
|
};
|
|
|
|
// chipsets that require the 'delay hack'
|
|
static int sis_broken_chipsets[] __devinitdata = {
|
|
PCI_DEVICE_ID_SI_648,
|
|
PCI_DEVICE_ID_SI_746,
|
|
0 // terminator
|
|
};
|
|
|
|
static void __devinit sis_get_driver(struct agp_bridge_data *bridge)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; sis_broken_chipsets[i]!=0; ++i)
|
|
if (bridge->dev->device==sis_broken_chipsets[i])
|
|
break;
|
|
|
|
if (sis_broken_chipsets[i] || agp_sis_force_delay)
|
|
sis_driver.agp_enable=sis_delayed_enable;
|
|
|
|
// sis chipsets that indicate less than agp3.5
|
|
// are not actually fully agp3 compliant
|
|
if ((agp_bridge->major_version == 3 && agp_bridge->minor_version >= 5
|
|
&& agp_sis_agp_spec!=0) || agp_sis_agp_spec==1) {
|
|
sis_driver.aperture_sizes = agp3_generic_sizes;
|
|
sis_driver.size_type = U16_APER_SIZE;
|
|
sis_driver.num_aperture_sizes = AGP_GENERIC_SIZES_ENTRIES;
|
|
sis_driver.configure = agp3_generic_configure;
|
|
sis_driver.fetch_size = agp3_generic_fetch_size;
|
|
sis_driver.cleanup = agp3_generic_cleanup;
|
|
sis_driver.tlb_flush = agp3_generic_tlbflush;
|
|
}
|
|
}
|
|
|
|
|
|
static int __devinit agp_sis_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
struct agp_bridge_data *bridge;
|
|
u8 cap_ptr;
|
|
|
|
cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
|
|
if (!cap_ptr)
|
|
return -ENODEV;
|
|
|
|
|
|
printk(KERN_INFO PFX "Detected SiS chipset - id:%i\n", pdev->device);
|
|
bridge = agp_alloc_bridge();
|
|
if (!bridge)
|
|
return -ENOMEM;
|
|
|
|
bridge->driver = &sis_driver;
|
|
bridge->dev = pdev;
|
|
bridge->capndx = cap_ptr;
|
|
|
|
get_agp_version(bridge);
|
|
|
|
/* Fill in the mode register */
|
|
pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
|
|
sis_get_driver(bridge);
|
|
|
|
pci_set_drvdata(pdev, bridge);
|
|
return agp_add_bridge(bridge);
|
|
}
|
|
|
|
static void __devexit agp_sis_remove(struct pci_dev *pdev)
|
|
{
|
|
struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
|
|
|
|
agp_remove_bridge(bridge);
|
|
agp_put_bridge(bridge);
|
|
}
|
|
|
|
static struct pci_device_id agp_sis_pci_table[] = {
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_5591_AGP,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_530,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_540,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_550,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_620,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_630,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_635,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_645,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_646,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_648,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_650,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_651,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_655,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_661,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_730,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_735,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_740,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_741,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_745,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_746,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{
|
|
.class = (PCI_CLASS_BRIDGE_HOST << 8),
|
|
.class_mask = ~0,
|
|
.vendor = PCI_VENDOR_ID_SI,
|
|
.device = PCI_DEVICE_ID_SI_760,
|
|
.subvendor = PCI_ANY_ID,
|
|
.subdevice = PCI_ANY_ID,
|
|
},
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, agp_sis_pci_table);
|
|
|
|
static struct pci_driver agp_sis_pci_driver = {
|
|
.name = "agpgart-sis",
|
|
.id_table = agp_sis_pci_table,
|
|
.probe = agp_sis_probe,
|
|
.remove = agp_sis_remove,
|
|
};
|
|
|
|
static int __init agp_sis_init(void)
|
|
{
|
|
if (agp_off)
|
|
return -EINVAL;
|
|
return pci_register_driver(&agp_sis_pci_driver);
|
|
}
|
|
|
|
static void __exit agp_sis_cleanup(void)
|
|
{
|
|
pci_unregister_driver(&agp_sis_pci_driver);
|
|
}
|
|
|
|
module_init(agp_sis_init);
|
|
module_exit(agp_sis_cleanup);
|
|
|
|
module_param(agp_sis_force_delay, bool, 0);
|
|
MODULE_PARM_DESC(agp_sis_force_delay,"forces sis delay hack");
|
|
module_param(agp_sis_agp_spec, int, 0);
|
|
MODULE_PARM_DESC(agp_sis_agp_spec,"0=force sis init, 1=force generic agp3 init, default: autodetect");
|
|
MODULE_LICENSE("GPL and additional rights");
|