11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * This file contains quirk handling code for PnP devices 31da177e4SLinus Torvalds * Some devices do not report all their resources, and need to have extra 41da177e4SLinus Torvalds * resources added. This is most easily accomplished at initialisation time 51da177e4SLinus Torvalds * when building up the resource structure for the first time. 61da177e4SLinus Torvalds * 71da177e4SLinus Torvalds * Copyright (c) 2000 Peter Denison <peterd@pnd-pc.demon.co.uk> 81f32ca31SBjorn Helgaas * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. 91f32ca31SBjorn Helgaas * Bjorn Helgaas <bjorn.helgaas@hp.com> 101da177e4SLinus Torvalds * 111da177e4SLinus Torvalds * Heavily based on PCI quirks handling which is 121da177e4SLinus Torvalds * 131da177e4SLinus Torvalds * Copyright (c) 1999 Martin Mares <mj@ucw.cz> 141da177e4SLinus Torvalds */ 151da177e4SLinus Torvalds 161da177e4SLinus Torvalds #include <linux/types.h> 171da177e4SLinus Torvalds #include <linux/kernel.h> 181da177e4SLinus Torvalds #include <linux/string.h> 191da177e4SLinus Torvalds #include <linux/slab.h> 201da177e4SLinus Torvalds #include <linux/pnp.h> 21a1e7e636SBjorn Helgaas #include <linux/io.h> 22a05d0781SBjorn Helgaas #include <linux/kallsyms.h> 231da177e4SLinus Torvalds #include "base.h" 241da177e4SLinus Torvalds 251f32ca31SBjorn Helgaas static void quirk_awe32_add_ports(struct pnp_dev *dev, 261f32ca31SBjorn Helgaas struct pnp_option *option, 271f32ca31SBjorn Helgaas unsigned int offset) 281da177e4SLinus Torvalds { 291f32ca31SBjorn Helgaas struct pnp_option *new_option; 301da177e4SLinus Torvalds 311f32ca31SBjorn Helgaas new_option = kmalloc(sizeof(struct pnp_option), GFP_KERNEL); 321f32ca31SBjorn Helgaas if (!new_option) { 331f32ca31SBjorn Helgaas dev_err(&dev->dev, "couldn't add ioport region to option set " 341f32ca31SBjorn Helgaas "%d\n", pnp_option_set(option)); 351da177e4SLinus Torvalds return; 361da177e4SLinus Torvalds } 371f32ca31SBjorn Helgaas 381f32ca31SBjorn Helgaas *new_option = *option; 391f32ca31SBjorn Helgaas new_option->u.port.min += offset; 401f32ca31SBjorn Helgaas new_option->u.port.max += offset; 411f32ca31SBjorn Helgaas list_add(&new_option->list, &option->list); 421f32ca31SBjorn Helgaas 431f32ca31SBjorn Helgaas dev_info(&dev->dev, "added ioport region %#llx-%#llx to set %d\n", 441f32ca31SBjorn Helgaas (unsigned long long) new_option->u.port.min, 451f32ca31SBjorn Helgaas (unsigned long long) new_option->u.port.max, 461f32ca31SBjorn Helgaas pnp_option_set(option)); 471f32ca31SBjorn Helgaas } 481f32ca31SBjorn Helgaas 491f32ca31SBjorn Helgaas static void quirk_awe32_resources(struct pnp_dev *dev) 501f32ca31SBjorn Helgaas { 511f32ca31SBjorn Helgaas struct pnp_option *option; 521f32ca31SBjorn Helgaas unsigned int set = ~0; 531f32ca31SBjorn Helgaas 541f32ca31SBjorn Helgaas /* 551f32ca31SBjorn Helgaas * Add two extra ioport regions (at offset 0x400 and 0x800 from the 561f32ca31SBjorn Helgaas * one given) to every dependent option set. 571f32ca31SBjorn Helgaas */ 581f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 591f32ca31SBjorn Helgaas if (pnp_option_is_dependent(option) && 601f32ca31SBjorn Helgaas pnp_option_set(option) != set) { 611f32ca31SBjorn Helgaas set = pnp_option_set(option); 621f32ca31SBjorn Helgaas quirk_awe32_add_ports(dev, option, 0x800); 631f32ca31SBjorn Helgaas quirk_awe32_add_ports(dev, option, 0x400); 641f32ca31SBjorn Helgaas } 651da177e4SLinus Torvalds } 661da177e4SLinus Torvalds } 671da177e4SLinus Torvalds 681da177e4SLinus Torvalds static void quirk_cmi8330_resources(struct pnp_dev *dev) 691da177e4SLinus Torvalds { 701f32ca31SBjorn Helgaas struct pnp_option *option; 711da177e4SLinus Torvalds struct pnp_irq *irq; 721da177e4SLinus Torvalds struct pnp_dma *dma; 731da177e4SLinus Torvalds 741f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 751f32ca31SBjorn Helgaas if (!pnp_option_is_dependent(option)) 761f32ca31SBjorn Helgaas continue; 771da177e4SLinus Torvalds 781f32ca31SBjorn Helgaas if (option->type == IORESOURCE_IRQ) { 791f32ca31SBjorn Helgaas irq = &option->u.irq; 801f32ca31SBjorn Helgaas bitmap_zero(irq->map.bits, PNP_IRQ_NR); 811f32ca31SBjorn Helgaas __set_bit(5, irq->map.bits); 821f32ca31SBjorn Helgaas __set_bit(7, irq->map.bits); 831f32ca31SBjorn Helgaas __set_bit(10, irq->map.bits); 841f32ca31SBjorn Helgaas dev_info(&dev->dev, "set possible IRQs in " 851f32ca31SBjorn Helgaas "option set %d to 5, 7, 10\n", 861f32ca31SBjorn Helgaas pnp_option_set(option)); 871f32ca31SBjorn Helgaas } else if (option->type == IORESOURCE_DMA) { 881f32ca31SBjorn Helgaas dma = &option->u.dma; 899dd78466SBjorn Helgaas if ((dma->flags & IORESOURCE_DMA_TYPE_MASK) == 901f32ca31SBjorn Helgaas IORESOURCE_DMA_8BIT && 911f32ca31SBjorn Helgaas dma->map != 0x0A) { 921f32ca31SBjorn Helgaas dev_info(&dev->dev, "changing possible " 931f32ca31SBjorn Helgaas "DMA channel mask in option set %d " 941f32ca31SBjorn Helgaas "from %#02x to 0x0A (1, 3)\n", 951f32ca31SBjorn Helgaas pnp_option_set(option), dma->map); 961f32ca31SBjorn Helgaas dma->map = 0x0A; 971da177e4SLinus Torvalds } 987aefff51SBjorn Helgaas } 991f32ca31SBjorn Helgaas } 1001da177e4SLinus Torvalds } 1011da177e4SLinus Torvalds 1021da177e4SLinus Torvalds static void quirk_sb16audio_resources(struct pnp_dev *dev) 1031da177e4SLinus Torvalds { 1041f32ca31SBjorn Helgaas struct pnp_option *option; 1051f32ca31SBjorn Helgaas unsigned int prev_option_flags = ~0, n = 0; 1061da177e4SLinus Torvalds struct pnp_port *port; 1071da177e4SLinus Torvalds 1081da177e4SLinus Torvalds /* 1091f32ca31SBjorn Helgaas * The default range on the OPL port for these devices is 0x388-0x388. 1101da177e4SLinus Torvalds * Here we increase that range so that two such cards can be 1111da177e4SLinus Torvalds * auto-configured. 1121da177e4SLinus Torvalds */ 1131f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 1141f32ca31SBjorn Helgaas if (prev_option_flags != option->flags) { 1151f32ca31SBjorn Helgaas prev_option_flags = option->flags; 1161f32ca31SBjorn Helgaas n = 0; 1171f32ca31SBjorn Helgaas } 1181da177e4SLinus Torvalds 1191f32ca31SBjorn Helgaas if (pnp_option_is_dependent(option) && 1201f32ca31SBjorn Helgaas option->type == IORESOURCE_IO) { 1211f32ca31SBjorn Helgaas n++; 1221f32ca31SBjorn Helgaas port = &option->u.port; 1231f32ca31SBjorn Helgaas if (n == 3 && port->min == port->max) { 1241da177e4SLinus Torvalds port->max += 0x70; 1251f32ca31SBjorn Helgaas dev_info(&dev->dev, "increased option port " 1261f32ca31SBjorn Helgaas "range from %#llx-%#llx to " 1271f32ca31SBjorn Helgaas "%#llx-%#llx\n", 1281f32ca31SBjorn Helgaas (unsigned long long) port->min, 1291f32ca31SBjorn Helgaas (unsigned long long) port->min, 1301f32ca31SBjorn Helgaas (unsigned long long) port->min, 1311f32ca31SBjorn Helgaas (unsigned long long) port->max); 1321da177e4SLinus Torvalds } 1331f32ca31SBjorn Helgaas } 1341f32ca31SBjorn Helgaas } 1351da177e4SLinus Torvalds } 1361da177e4SLinus Torvalds 1371f32ca31SBjorn Helgaas static struct pnp_option *pnp_clone_dependent_set(struct pnp_dev *dev, 1381f32ca31SBjorn Helgaas unsigned int set) 1393b73a223SRene Herman { 1401f32ca31SBjorn Helgaas struct pnp_option *tail = NULL, *first_new_option = NULL; 1411f32ca31SBjorn Helgaas struct pnp_option *option, *new_option; 1421f32ca31SBjorn Helgaas unsigned int flags; 1433b73a223SRene Herman 1441f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 1451f32ca31SBjorn Helgaas if (pnp_option_is_dependent(option)) 1461f32ca31SBjorn Helgaas tail = option; 1471f32ca31SBjorn Helgaas } 1481f32ca31SBjorn Helgaas if (!tail) { 1491f32ca31SBjorn Helgaas dev_err(&dev->dev, "no dependent option sets\n"); 1501f32ca31SBjorn Helgaas return NULL; 1511f32ca31SBjorn Helgaas } 1523b73a223SRene Herman 1531f32ca31SBjorn Helgaas flags = pnp_new_dependent_set(dev, PNP_RES_PRIORITY_FUNCTIONAL); 1541f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 1551f32ca31SBjorn Helgaas if (pnp_option_is_dependent(option) && 1561f32ca31SBjorn Helgaas pnp_option_set(option) == set) { 1571f32ca31SBjorn Helgaas new_option = kmalloc(sizeof(struct pnp_option), 1581f32ca31SBjorn Helgaas GFP_KERNEL); 1591f32ca31SBjorn Helgaas if (!new_option) { 1601f32ca31SBjorn Helgaas dev_err(&dev->dev, "couldn't clone dependent " 1611f32ca31SBjorn Helgaas "set %d\n", set); 1621f32ca31SBjorn Helgaas return NULL; 1631f32ca31SBjorn Helgaas } 1641f32ca31SBjorn Helgaas 1651f32ca31SBjorn Helgaas *new_option = *option; 1661f32ca31SBjorn Helgaas new_option->flags = flags; 1671f32ca31SBjorn Helgaas if (!first_new_option) 1681f32ca31SBjorn Helgaas first_new_option = new_option; 1691f32ca31SBjorn Helgaas 1701f32ca31SBjorn Helgaas list_add(&new_option->list, &tail->list); 1711f32ca31SBjorn Helgaas tail = new_option; 1721f32ca31SBjorn Helgaas } 1731f32ca31SBjorn Helgaas } 1741f32ca31SBjorn Helgaas 1751f32ca31SBjorn Helgaas return first_new_option; 1761f32ca31SBjorn Helgaas } 1771f32ca31SBjorn Helgaas 1781f32ca31SBjorn Helgaas 1791f32ca31SBjorn Helgaas static void quirk_add_irq_optional_dependent_sets(struct pnp_dev *dev) 1801f32ca31SBjorn Helgaas { 1811f32ca31SBjorn Helgaas struct pnp_option *new_option; 1821f32ca31SBjorn Helgaas unsigned int num_sets, i, set; 183d5ebde6eSBjorn Helgaas struct pnp_irq *irq; 1843b73a223SRene Herman 1851f32ca31SBjorn Helgaas num_sets = dev->num_dependent_sets; 1861f32ca31SBjorn Helgaas for (i = 0; i < num_sets; i++) { 1871f32ca31SBjorn Helgaas new_option = pnp_clone_dependent_set(dev, i); 1881f32ca31SBjorn Helgaas if (!new_option) 1891f32ca31SBjorn Helgaas return; 1903b73a223SRene Herman 1911f32ca31SBjorn Helgaas set = pnp_option_set(new_option); 1921f32ca31SBjorn Helgaas while (new_option && pnp_option_set(new_option) == set) { 1931f32ca31SBjorn Helgaas if (new_option->type == IORESOURCE_IRQ) { 1941f32ca31SBjorn Helgaas irq = &new_option->u.irq; 1951f32ca31SBjorn Helgaas irq->flags |= IORESOURCE_IRQ_OPTIONAL; 1961f32ca31SBjorn Helgaas } 1971f32ca31SBjorn Helgaas dbg_pnp_show_option(dev, new_option); 1981f32ca31SBjorn Helgaas new_option = list_entry(new_option->list.next, 1991f32ca31SBjorn Helgaas struct pnp_option, list); 200d5ebde6eSBjorn Helgaas } 201d5ebde6eSBjorn Helgaas 2021f32ca31SBjorn Helgaas dev_info(&dev->dev, "added dependent option set %d (same as " 2031f32ca31SBjorn Helgaas "set %d except IRQ optional)\n", set, i); 2043b73a223SRene Herman } 2053b73a223SRene Herman } 2063b73a223SRene Herman 2073b73a223SRene Herman static void quirk_ad1815_mpu_resources(struct pnp_dev *dev) 2083b73a223SRene Herman { 2091f32ca31SBjorn Helgaas struct pnp_option *option; 2101f32ca31SBjorn Helgaas struct pnp_irq *irq = NULL; 2111f32ca31SBjorn Helgaas unsigned int independent_irqs = 0; 2123b73a223SRene Herman 2131f32ca31SBjorn Helgaas list_for_each_entry(option, &dev->options, list) { 2141f32ca31SBjorn Helgaas if (option->type == IORESOURCE_IRQ && 2151f32ca31SBjorn Helgaas !pnp_option_is_dependent(option)) { 2161f32ca31SBjorn Helgaas independent_irqs++; 2171f32ca31SBjorn Helgaas irq = &option->u.irq; 2181f32ca31SBjorn Helgaas } 2191f32ca31SBjorn Helgaas } 2203b73a223SRene Herman 2211f32ca31SBjorn Helgaas if (independent_irqs != 1) 2223b73a223SRene Herman return; 2233b73a223SRene Herman 224d5ebde6eSBjorn Helgaas irq->flags |= IORESOURCE_IRQ_OPTIONAL; 225d5ebde6eSBjorn Helgaas dev_info(&dev->dev, "made independent IRQ optional\n"); 2263b73a223SRene Herman } 2273b73a223SRene Herman 2280509ad5eSBjorn Helgaas #include <linux/pci.h> 2290509ad5eSBjorn Helgaas 2300509ad5eSBjorn Helgaas static void quirk_system_pci_resources(struct pnp_dev *dev) 2310509ad5eSBjorn Helgaas { 2320509ad5eSBjorn Helgaas struct pci_dev *pdev = NULL; 23353052febSBjorn Helgaas struct resource *res; 2340509ad5eSBjorn Helgaas resource_size_t pnp_start, pnp_end, pci_start, pci_end; 2350509ad5eSBjorn Helgaas int i, j; 2360509ad5eSBjorn Helgaas 2370509ad5eSBjorn Helgaas /* 2380509ad5eSBjorn Helgaas * Some BIOSes have PNP motherboard devices with resources that 2390509ad5eSBjorn Helgaas * partially overlap PCI BARs. The PNP system driver claims these 2400509ad5eSBjorn Helgaas * motherboard resources, which prevents the normal PCI driver from 2410509ad5eSBjorn Helgaas * requesting them later. 2420509ad5eSBjorn Helgaas * 2430509ad5eSBjorn Helgaas * This patch disables the PNP resources that conflict with PCI BARs 2440509ad5eSBjorn Helgaas * so they won't be claimed by the PNP system driver. 2450509ad5eSBjorn Helgaas */ 2460509ad5eSBjorn Helgaas for_each_pci_dev(pdev) { 2470509ad5eSBjorn Helgaas for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { 248b563cf59SRene Herman unsigned long type; 249999ed65aSRene Herman 250999ed65aSRene Herman type = pci_resource_flags(pdev, i) & 251999ed65aSRene Herman (IORESOURCE_IO | IORESOURCE_MEM); 252999ed65aSRene Herman if (!type || pci_resource_len(pdev, i) == 0) 2530509ad5eSBjorn Helgaas continue; 2540509ad5eSBjorn Helgaas 2550509ad5eSBjorn Helgaas pci_start = pci_resource_start(pdev, i); 2560509ad5eSBjorn Helgaas pci_end = pci_resource_end(pdev, i); 25795ab3669SBjorn Helgaas for (j = 0; 258999ed65aSRene Herman (res = pnp_get_resource(dev, type, j)); j++) { 259aee3ad81SBjorn Helgaas if (res->start == 0 && res->end == 0) 2600509ad5eSBjorn Helgaas continue; 2610509ad5eSBjorn Helgaas 26295ab3669SBjorn Helgaas pnp_start = res->start; 26395ab3669SBjorn Helgaas pnp_end = res->end; 2640509ad5eSBjorn Helgaas 2650509ad5eSBjorn Helgaas /* 2660509ad5eSBjorn Helgaas * If the PNP region doesn't overlap the PCI 2670509ad5eSBjorn Helgaas * region at all, there's no problem. 2680509ad5eSBjorn Helgaas */ 2690509ad5eSBjorn Helgaas if (pnp_end < pci_start || pnp_start > pci_end) 2700509ad5eSBjorn Helgaas continue; 2710509ad5eSBjorn Helgaas 2720509ad5eSBjorn Helgaas /* 2730509ad5eSBjorn Helgaas * If the PNP region completely encloses (or is 2740509ad5eSBjorn Helgaas * at least as large as) the PCI region, that's 2750509ad5eSBjorn Helgaas * also OK. For example, this happens when the 2760509ad5eSBjorn Helgaas * PNP device describes a bridge with PCI 2770509ad5eSBjorn Helgaas * behind it. 2780509ad5eSBjorn Helgaas */ 2790509ad5eSBjorn Helgaas if (pnp_start <= pci_start && 2800509ad5eSBjorn Helgaas pnp_end >= pci_end) 2810509ad5eSBjorn Helgaas continue; 2820509ad5eSBjorn Helgaas 2830509ad5eSBjorn Helgaas /* 2840509ad5eSBjorn Helgaas * Otherwise, the PNP region overlaps *part* of 2850509ad5eSBjorn Helgaas * the PCI region, and that might prevent a PCI 2860509ad5eSBjorn Helgaas * driver from requesting its resources. 2870509ad5eSBjorn Helgaas */ 288c7dabef8SBjorn Helgaas dev_warn(&dev->dev, 289c7dabef8SBjorn Helgaas "disabling %pR because it overlaps " 290c7dabef8SBjorn Helgaas "%s BAR %d %pR\n", res, 2919a007b37SBjorn Helgaas pci_name(pdev), i, &pdev->resource[i]); 2924b34fe15SBjorn Helgaas res->flags |= IORESOURCE_DISABLED; 2930509ad5eSBjorn Helgaas } 2940509ad5eSBjorn Helgaas } 2950509ad5eSBjorn Helgaas } 2960509ad5eSBjorn Helgaas } 2970509ad5eSBjorn Helgaas 2981da177e4SLinus Torvalds /* 2991da177e4SLinus Torvalds * PnP Quirks 3001da177e4SLinus Torvalds * Cards or devices that need some tweaking due to incomplete resource info 3011da177e4SLinus Torvalds */ 3021da177e4SLinus Torvalds 3031da177e4SLinus Torvalds static struct pnp_fixup pnp_fixups[] = { 3041da177e4SLinus Torvalds /* Soundblaster awe io port quirk */ 3051da177e4SLinus Torvalds {"CTL0021", quirk_awe32_resources}, 3061da177e4SLinus Torvalds {"CTL0022", quirk_awe32_resources}, 3071da177e4SLinus Torvalds {"CTL0023", quirk_awe32_resources}, 3081da177e4SLinus Torvalds /* CMI 8330 interrupt and dma fix */ 3091da177e4SLinus Torvalds {"@X@0001", quirk_cmi8330_resources}, 3101da177e4SLinus Torvalds /* Soundblaster audio device io port range quirk */ 3111da177e4SLinus Torvalds {"CTL0001", quirk_sb16audio_resources}, 3121da177e4SLinus Torvalds {"CTL0031", quirk_sb16audio_resources}, 3131da177e4SLinus Torvalds {"CTL0041", quirk_sb16audio_resources}, 3141da177e4SLinus Torvalds {"CTL0042", quirk_sb16audio_resources}, 3151da177e4SLinus Torvalds {"CTL0043", quirk_sb16audio_resources}, 3161da177e4SLinus Torvalds {"CTL0044", quirk_sb16audio_resources}, 3171da177e4SLinus Torvalds {"CTL0045", quirk_sb16audio_resources}, 3181f32ca31SBjorn Helgaas /* Add IRQ-optional MPU options */ 3193b73a223SRene Herman {"ADS7151", quirk_ad1815_mpu_resources}, 3201f32ca31SBjorn Helgaas {"ADS7181", quirk_add_irq_optional_dependent_sets}, 3211f32ca31SBjorn Helgaas {"AZT0002", quirk_add_irq_optional_dependent_sets}, 3223b73a223SRene Herman /* PnP resources that might overlap PCI BARs */ 3230509ad5eSBjorn Helgaas {"PNP0c01", quirk_system_pci_resources}, 3240509ad5eSBjorn Helgaas {"PNP0c02", quirk_system_pci_resources}, 3251da177e4SLinus Torvalds {""} 3261da177e4SLinus Torvalds }; 3271da177e4SLinus Torvalds 3281da177e4SLinus Torvalds void pnp_fixup_device(struct pnp_dev *dev) 3291da177e4SLinus Torvalds { 330726a7a3dSRene Herman struct pnp_fixup *f; 3311da177e4SLinus Torvalds 332726a7a3dSRene Herman for (f = pnp_fixups; *f->id; f++) { 333726a7a3dSRene Herman if (!compare_pnp_id(dev->id, f->id)) 334726a7a3dSRene Herman continue; 3352f53432cSBjorn Helgaas pnp_dbg(&dev->dev, "%s: calling %pF\n", f->id, 336668b21deSBjorn Helgaas f->quirk_function); 337726a7a3dSRene Herman f->quirk_function(dev); 3381da177e4SLinus Torvalds } 3391da177e4SLinus Torvalds } 340