xref: /openbmc/linux/drivers/pnp/quirks.c (revision d5ebde6e)
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>
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  *  Heavily based on PCI quirks handling which is
101da177e4SLinus Torvalds  *
111da177e4SLinus Torvalds  *  Copyright (c) 1999 Martin Mares <mj@ucw.cz>
121da177e4SLinus Torvalds  */
131da177e4SLinus Torvalds 
141da177e4SLinus Torvalds #include <linux/types.h>
151da177e4SLinus Torvalds #include <linux/kernel.h>
161da177e4SLinus Torvalds #include <linux/string.h>
171da177e4SLinus Torvalds #include <linux/slab.h>
181da177e4SLinus Torvalds #include <linux/pnp.h>
19a1e7e636SBjorn Helgaas #include <linux/io.h>
20a05d0781SBjorn Helgaas #include <linux/kallsyms.h>
211da177e4SLinus Torvalds #include "base.h"
221da177e4SLinus Torvalds 
231da177e4SLinus Torvalds static void quirk_awe32_resources(struct pnp_dev *dev)
241da177e4SLinus Torvalds {
251da177e4SLinus Torvalds 	struct pnp_port *port, *port2, *port3;
261da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
271da177e4SLinus Torvalds 
281da177e4SLinus Torvalds 	/*
291da177e4SLinus Torvalds 	 * Unfortunately the isapnp_add_port_resource is too tightly bound
301da177e4SLinus Torvalds 	 * into the PnP discovery sequence, and cannot be used. Link in the
311da177e4SLinus Torvalds 	 * two extra ports (at offset 0x400 and 0x800 from the one given) by
321da177e4SLinus Torvalds 	 * hand.
331da177e4SLinus Torvalds 	 */
341da177e4SLinus Torvalds 	for (; res; res = res->next) {
351da177e4SLinus Torvalds 		port2 = pnp_alloc(sizeof(struct pnp_port));
361da177e4SLinus Torvalds 		if (!port2)
371da177e4SLinus Torvalds 			return;
381da177e4SLinus Torvalds 		port3 = pnp_alloc(sizeof(struct pnp_port));
391da177e4SLinus Torvalds 		if (!port3) {
401da177e4SLinus Torvalds 			kfree(port2);
411da177e4SLinus Torvalds 			return;
421da177e4SLinus Torvalds 		}
431da177e4SLinus Torvalds 		port = res->port;
441da177e4SLinus Torvalds 		memcpy(port2, port, sizeof(struct pnp_port));
451da177e4SLinus Torvalds 		memcpy(port3, port, sizeof(struct pnp_port));
461da177e4SLinus Torvalds 		port->next = port2;
471da177e4SLinus Torvalds 		port2->next = port3;
481da177e4SLinus Torvalds 		port2->min += 0x400;
491da177e4SLinus Torvalds 		port2->max += 0x400;
501da177e4SLinus Torvalds 		port3->min += 0x800;
511da177e4SLinus Torvalds 		port3->max += 0x800;
521e3832b0SBjorn Helgaas 		dev_info(&dev->dev,
531e3832b0SBjorn Helgaas 			"AWE32 quirk - added ioports 0x%lx and 0x%lx\n",
541e3832b0SBjorn Helgaas 			(unsigned long)port2->min,
551e3832b0SBjorn Helgaas 			(unsigned long)port3->min);
561da177e4SLinus Torvalds 	}
571da177e4SLinus Torvalds }
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds static void quirk_cmi8330_resources(struct pnp_dev *dev)
601da177e4SLinus Torvalds {
611da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
621da177e4SLinus Torvalds 	unsigned long tmp;
631da177e4SLinus Torvalds 
641da177e4SLinus Torvalds 	for (; res; res = res->next) {
651da177e4SLinus Torvalds 
661da177e4SLinus Torvalds 		struct pnp_irq *irq;
671da177e4SLinus Torvalds 		struct pnp_dma *dma;
681da177e4SLinus Torvalds 
697aefff51SBjorn Helgaas 		for (irq = res->irq; irq; irq = irq->next) {
707aefff51SBjorn Helgaas 			/* Valid irqs are 5, 7, 10 */
711da177e4SLinus Torvalds 			tmp = 0x04A0;
727aefff51SBjorn Helgaas 			bitmap_copy(irq->map.bits, &tmp, 16);
731da177e4SLinus Torvalds 		}
741da177e4SLinus Torvalds 
757aefff51SBjorn Helgaas 		for (dma = res->dma; dma; dma = dma->next) {
767aefff51SBjorn Helgaas 			/* Valid 8bit dma channels are 1,3 */
779dd78466SBjorn Helgaas 			if ((dma->flags & IORESOURCE_DMA_TYPE_MASK) ==
789dd78466SBjorn Helgaas 			    IORESOURCE_DMA_8BIT)
791da177e4SLinus Torvalds 				dma->map = 0x000A;
801da177e4SLinus Torvalds 		}
817aefff51SBjorn Helgaas 	}
821e3832b0SBjorn Helgaas 	dev_info(&dev->dev, "CMI8330 quirk - forced possible IRQs to 5, 7, 10 "
831e3832b0SBjorn Helgaas 		"and DMA channels to 1, 3\n");
841da177e4SLinus Torvalds }
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds static void quirk_sb16audio_resources(struct pnp_dev *dev)
871da177e4SLinus Torvalds {
881da177e4SLinus Torvalds 	struct pnp_port *port;
891da177e4SLinus Torvalds 	struct pnp_option *res = dev->dependent;
901da177e4SLinus Torvalds 	int changed = 0;
911da177e4SLinus Torvalds 
921da177e4SLinus Torvalds 	/*
931da177e4SLinus Torvalds 	 * The default range on the mpu port for these devices is 0x388-0x388.
941da177e4SLinus Torvalds 	 * Here we increase that range so that two such cards can be
951da177e4SLinus Torvalds 	 * auto-configured.
961da177e4SLinus Torvalds 	 */
971da177e4SLinus Torvalds 
981da177e4SLinus Torvalds 	for (; res; res = res->next) {
991da177e4SLinus Torvalds 		port = res->port;
1001da177e4SLinus Torvalds 		if (!port)
1011da177e4SLinus Torvalds 			continue;
1021da177e4SLinus Torvalds 		port = port->next;
1031da177e4SLinus Torvalds 		if (!port)
1041da177e4SLinus Torvalds 			continue;
1051da177e4SLinus Torvalds 		port = port->next;
1061da177e4SLinus Torvalds 		if (!port)
1071da177e4SLinus Torvalds 			continue;
1081da177e4SLinus Torvalds 		if (port->min != port->max)
1091da177e4SLinus Torvalds 			continue;
1101da177e4SLinus Torvalds 		port->max += 0x70;
1111da177e4SLinus Torvalds 		changed = 1;
1121da177e4SLinus Torvalds 	}
1131da177e4SLinus Torvalds 	if (changed)
1141e3832b0SBjorn Helgaas 		dev_info(&dev->dev, "SB audio device quirk - increased port range\n");
1151da177e4SLinus Torvalds }
1161da177e4SLinus Torvalds 
1173b73a223SRene Herman static struct pnp_option *quirk_isapnp_mpu_options(struct pnp_dev *dev)
1183b73a223SRene Herman {
1193b73a223SRene Herman 	struct pnp_option *head = NULL;
1203b73a223SRene Herman 	struct pnp_option *prev = NULL;
1213b73a223SRene Herman 	struct pnp_option *res;
1223b73a223SRene Herman 
1233b73a223SRene Herman 	/*
124d5ebde6eSBjorn Helgaas 	 * Build a functional IRQ-optional variant of each MPU option.
1253b73a223SRene Herman 	 */
1263b73a223SRene Herman 
1273b73a223SRene Herman 	for (res = dev->dependent; res; res = res->next) {
1283b73a223SRene Herman 		struct pnp_option *curr;
1293b73a223SRene Herman 		struct pnp_port *port;
130d5ebde6eSBjorn Helgaas 		struct pnp_port *copy_port;
131d5ebde6eSBjorn Helgaas 		struct pnp_irq *irq;
132d5ebde6eSBjorn Helgaas 		struct pnp_irq *copy_irq;
1333b73a223SRene Herman 
1343b73a223SRene Herman 		port = res->port;
135d5ebde6eSBjorn Helgaas 		irq = res->irq;
136d5ebde6eSBjorn Helgaas 		if (!port || !irq)
1373b73a223SRene Herman 			continue;
1383b73a223SRene Herman 
139d5ebde6eSBjorn Helgaas 		copy_port = pnp_alloc(sizeof *copy_port);
140d5ebde6eSBjorn Helgaas 		if (!copy_port)
1413b73a223SRene Herman 			break;
1423b73a223SRene Herman 
143d5ebde6eSBjorn Helgaas 		copy_irq = pnp_alloc(sizeof *copy_irq);
144d5ebde6eSBjorn Helgaas 		if (!copy_irq) {
145d5ebde6eSBjorn Helgaas 			kfree(copy_port);
146d5ebde6eSBjorn Helgaas 			break;
147d5ebde6eSBjorn Helgaas 		}
148d5ebde6eSBjorn Helgaas 
149d5ebde6eSBjorn Helgaas 		*copy_port = *port;
150d5ebde6eSBjorn Helgaas 		copy_port->next = NULL;
151d5ebde6eSBjorn Helgaas 
152d5ebde6eSBjorn Helgaas 		*copy_irq = *irq;
153d5ebde6eSBjorn Helgaas 		copy_irq->flags |= IORESOURCE_IRQ_OPTIONAL;
154d5ebde6eSBjorn Helgaas 		copy_irq->next = NULL;
1553b73a223SRene Herman 
1563b73a223SRene Herman 		curr = pnp_build_option(PNP_RES_PRIORITY_FUNCTIONAL);
1573b73a223SRene Herman 		if (!curr) {
158d5ebde6eSBjorn Helgaas 			kfree(copy_port);
159d5ebde6eSBjorn Helgaas 			kfree(copy_irq);
1603b73a223SRene Herman 			break;
1613b73a223SRene Herman 		}
162d5ebde6eSBjorn Helgaas 		curr->port = copy_port;
163d5ebde6eSBjorn Helgaas 		curr->irq = copy_irq;
1643b73a223SRene Herman 
1653b73a223SRene Herman 		if (prev)
1663b73a223SRene Herman 			prev->next = curr;
1673b73a223SRene Herman 		else
1683b73a223SRene Herman 			head = curr;
1693b73a223SRene Herman 		prev = curr;
1703b73a223SRene Herman 	}
1713b73a223SRene Herman 	if (head)
172d5ebde6eSBjorn Helgaas 		dev_info(&dev->dev, "adding IRQ-optional MPU options\n");
1733b73a223SRene Herman 
1743b73a223SRene Herman 	return head;
1753b73a223SRene Herman }
1763b73a223SRene Herman 
1773b73a223SRene Herman static void quirk_ad1815_mpu_resources(struct pnp_dev *dev)
1783b73a223SRene Herman {
1793b73a223SRene Herman 	struct pnp_option *res;
1803b73a223SRene Herman 	struct pnp_irq *irq;
1813b73a223SRene Herman 
1823b73a223SRene Herman 	res = dev->independent;
1833b73a223SRene Herman 	if (!res)
1843b73a223SRene Herman 		return;
1853b73a223SRene Herman 
1863b73a223SRene Herman 	irq = res->irq;
1873b73a223SRene Herman 	if (!irq || irq->next)
1883b73a223SRene Herman 		return;
1893b73a223SRene Herman 
190d5ebde6eSBjorn Helgaas 	irq->flags |= IORESOURCE_IRQ_OPTIONAL;
191d5ebde6eSBjorn Helgaas 	dev_info(&dev->dev, "made independent IRQ optional\n");
1923b73a223SRene Herman }
1933b73a223SRene Herman 
1943b73a223SRene Herman static void quirk_isapnp_mpu_resources(struct pnp_dev *dev)
1953b73a223SRene Herman {
1963b73a223SRene Herman 	struct pnp_option *res;
1973b73a223SRene Herman 
1983b73a223SRene Herman 	res = dev->dependent;
1993b73a223SRene Herman 	if (!res)
2003b73a223SRene Herman 		return;
2013b73a223SRene Herman 
2023b73a223SRene Herman 	while (res->next)
2033b73a223SRene Herman 		res = res->next;
2043b73a223SRene Herman 
2053b73a223SRene Herman 	res->next = quirk_isapnp_mpu_options(dev);
2063b73a223SRene Herman }
2070509ad5eSBjorn Helgaas 
2080509ad5eSBjorn Helgaas #include <linux/pci.h>
2090509ad5eSBjorn Helgaas 
2100509ad5eSBjorn Helgaas static void quirk_system_pci_resources(struct pnp_dev *dev)
2110509ad5eSBjorn Helgaas {
2120509ad5eSBjorn Helgaas 	struct pci_dev *pdev = NULL;
21353052febSBjorn Helgaas 	struct resource *res;
2140509ad5eSBjorn Helgaas 	resource_size_t pnp_start, pnp_end, pci_start, pci_end;
2150509ad5eSBjorn Helgaas 	int i, j;
2160509ad5eSBjorn Helgaas 
2170509ad5eSBjorn Helgaas 	/*
2180509ad5eSBjorn Helgaas 	 * Some BIOSes have PNP motherboard devices with resources that
2190509ad5eSBjorn Helgaas 	 * partially overlap PCI BARs.  The PNP system driver claims these
2200509ad5eSBjorn Helgaas 	 * motherboard resources, which prevents the normal PCI driver from
2210509ad5eSBjorn Helgaas 	 * requesting them later.
2220509ad5eSBjorn Helgaas 	 *
2230509ad5eSBjorn Helgaas 	 * This patch disables the PNP resources that conflict with PCI BARs
2240509ad5eSBjorn Helgaas 	 * so they won't be claimed by the PNP system driver.
2250509ad5eSBjorn Helgaas 	 */
2260509ad5eSBjorn Helgaas 	for_each_pci_dev(pdev) {
2270509ad5eSBjorn Helgaas 		for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
2280509ad5eSBjorn Helgaas 			if (!(pci_resource_flags(pdev, i) & IORESOURCE_MEM) ||
2290509ad5eSBjorn Helgaas 			    pci_resource_len(pdev, i) == 0)
2300509ad5eSBjorn Helgaas 				continue;
2310509ad5eSBjorn Helgaas 
2320509ad5eSBjorn Helgaas 			pci_start = pci_resource_start(pdev, i);
2330509ad5eSBjorn Helgaas 			pci_end = pci_resource_end(pdev, i);
23495ab3669SBjorn Helgaas 			for (j = 0;
23595ab3669SBjorn Helgaas 			     (res = pnp_get_resource(dev, IORESOURCE_MEM, j));
23695ab3669SBjorn Helgaas 			     j++) {
237aee3ad81SBjorn Helgaas 				if (res->start == 0 && res->end == 0)
2380509ad5eSBjorn Helgaas 					continue;
2390509ad5eSBjorn Helgaas 
24095ab3669SBjorn Helgaas 				pnp_start = res->start;
24195ab3669SBjorn Helgaas 				pnp_end = res->end;
2420509ad5eSBjorn Helgaas 
2430509ad5eSBjorn Helgaas 				/*
2440509ad5eSBjorn Helgaas 				 * If the PNP region doesn't overlap the PCI
2450509ad5eSBjorn Helgaas 				 * region at all, there's no problem.
2460509ad5eSBjorn Helgaas 				 */
2470509ad5eSBjorn Helgaas 				if (pnp_end < pci_start || pnp_start > pci_end)
2480509ad5eSBjorn Helgaas 					continue;
2490509ad5eSBjorn Helgaas 
2500509ad5eSBjorn Helgaas 				/*
2510509ad5eSBjorn Helgaas 				 * If the PNP region completely encloses (or is
2520509ad5eSBjorn Helgaas 				 * at least as large as) the PCI region, that's
2530509ad5eSBjorn Helgaas 				 * also OK.  For example, this happens when the
2540509ad5eSBjorn Helgaas 				 * PNP device describes a bridge with PCI
2550509ad5eSBjorn Helgaas 				 * behind it.
2560509ad5eSBjorn Helgaas 				 */
2570509ad5eSBjorn Helgaas 				if (pnp_start <= pci_start &&
2580509ad5eSBjorn Helgaas 				    pnp_end >= pci_end)
2590509ad5eSBjorn Helgaas 					continue;
2600509ad5eSBjorn Helgaas 
2610509ad5eSBjorn Helgaas 				/*
2620509ad5eSBjorn Helgaas 				 * Otherwise, the PNP region overlaps *part* of
2630509ad5eSBjorn Helgaas 				 * the PCI region, and that might prevent a PCI
2640509ad5eSBjorn Helgaas 				 * driver from requesting its resources.
2650509ad5eSBjorn Helgaas 				 */
2660509ad5eSBjorn Helgaas 				dev_warn(&dev->dev, "mem resource "
2670509ad5eSBjorn Helgaas 					"(0x%llx-0x%llx) overlaps %s BAR %d "
2680509ad5eSBjorn Helgaas 					"(0x%llx-0x%llx), disabling\n",
2690509ad5eSBjorn Helgaas 					(unsigned long long) pnp_start,
2700509ad5eSBjorn Helgaas 					(unsigned long long) pnp_end,
2710509ad5eSBjorn Helgaas 					pci_name(pdev), i,
2720509ad5eSBjorn Helgaas 					(unsigned long long) pci_start,
2730509ad5eSBjorn Helgaas 					(unsigned long long) pci_end);
2744b34fe15SBjorn Helgaas 				res->flags |= IORESOURCE_DISABLED;
2750509ad5eSBjorn Helgaas 			}
2760509ad5eSBjorn Helgaas 		}
2770509ad5eSBjorn Helgaas 	}
2780509ad5eSBjorn Helgaas }
2790509ad5eSBjorn Helgaas 
2801da177e4SLinus Torvalds /*
2811da177e4SLinus Torvalds  *  PnP Quirks
2821da177e4SLinus Torvalds  *  Cards or devices that need some tweaking due to incomplete resource info
2831da177e4SLinus Torvalds  */
2841da177e4SLinus Torvalds 
2851da177e4SLinus Torvalds static struct pnp_fixup pnp_fixups[] = {
2861da177e4SLinus Torvalds 	/* Soundblaster awe io port quirk */
2871da177e4SLinus Torvalds 	{"CTL0021", quirk_awe32_resources},
2881da177e4SLinus Torvalds 	{"CTL0022", quirk_awe32_resources},
2891da177e4SLinus Torvalds 	{"CTL0023", quirk_awe32_resources},
2901da177e4SLinus Torvalds 	/* CMI 8330 interrupt and dma fix */
2911da177e4SLinus Torvalds 	{"@X@0001", quirk_cmi8330_resources},
2921da177e4SLinus Torvalds 	/* Soundblaster audio device io port range quirk */
2931da177e4SLinus Torvalds 	{"CTL0001", quirk_sb16audio_resources},
2941da177e4SLinus Torvalds 	{"CTL0031", quirk_sb16audio_resources},
2951da177e4SLinus Torvalds 	{"CTL0041", quirk_sb16audio_resources},
2961da177e4SLinus Torvalds 	{"CTL0042", quirk_sb16audio_resources},
2971da177e4SLinus Torvalds 	{"CTL0043", quirk_sb16audio_resources},
2981da177e4SLinus Torvalds 	{"CTL0044", quirk_sb16audio_resources},
2991da177e4SLinus Torvalds 	{"CTL0045", quirk_sb16audio_resources},
3003b73a223SRene Herman 	/* Add IRQ-less MPU options */
3013b73a223SRene Herman 	{"ADS7151", quirk_ad1815_mpu_resources},
3023b73a223SRene Herman 	{"ADS7181", quirk_isapnp_mpu_resources},
3033b73a223SRene Herman 	{"AZT0002", quirk_isapnp_mpu_resources},
3043b73a223SRene Herman 	/* PnP resources that might overlap PCI BARs */
3050509ad5eSBjorn Helgaas 	{"PNP0c01", quirk_system_pci_resources},
3060509ad5eSBjorn Helgaas 	{"PNP0c02", quirk_system_pci_resources},
3071da177e4SLinus Torvalds 	{""}
3081da177e4SLinus Torvalds };
3091da177e4SLinus Torvalds 
3101da177e4SLinus Torvalds void pnp_fixup_device(struct pnp_dev *dev)
3111da177e4SLinus Torvalds {
312726a7a3dSRene Herman 	struct pnp_fixup *f;
3131da177e4SLinus Torvalds 
314726a7a3dSRene Herman 	for (f = pnp_fixups; *f->id; f++) {
315726a7a3dSRene Herman 		if (!compare_pnp_id(dev->id, f->id))
316726a7a3dSRene Herman 			continue;
317a05d0781SBjorn Helgaas #ifdef DEBUG
318726a7a3dSRene Herman 		dev_dbg(&dev->dev, "%s: calling ", f->id);
319a442ac51SLinus Torvalds 		print_fn_descriptor_symbol("%s\n", f->quirk_function);
320a05d0781SBjorn Helgaas #endif
321726a7a3dSRene Herman 		f->quirk_function(dev);
3221da177e4SLinus Torvalds 	}
3231da177e4SLinus Torvalds }
324